<#
****************************************************************************************
* Powershell Script GUI Network Scanner *
* Scans user input subnet and returns all active devices' IP Address *
* hostname, MAC Address, and OS version if applicable. *
* User can select field and right mouse click to copy data. *
* Version 2: WPF GUI *
* Coded by: djl236 *
* 2025 January 11 *
* Version 2.0 *
****************************************************************************************
#>
#Load WPF Assembly
Add-Type -AssemblyName PresentationFramework
#Define the WPF XAML Layout
$Xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Network Scanner Tool"
Height="520" Width="750" WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Header -->
<RowDefinition Height="Auto"/> <!-- Subnet Input -->
<RowDefinition Height="*"/> <!-- Results -->
<RowDefinition Height="Auto"/> <!-- Status -->
<RowDefinition Height="Auto"/> <!-- Footer -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- Label -->
<ColumnDefinition Width="Auto"/> <!-- Input -->
<ColumnDefinition Width="Auto"/> <!-- Scan -->
<ColumnDefinition Width="Auto"/> <!-- Clear -->
<ColumnDefinition Width="Auto"/> <!-- Close -->
<ColumnDefinition Width="*"/> <!-- Remaining space -->
</Grid.ColumnDefinitions>
<!-- Header -->
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="6" HorizontalAlignment="Center"
VerticalAlignment="Center" FontSize="14" FontWeight="Bold">
<TextBlock TextAlignment="Center">
Network Scanner Tool
<LineBreak/>
Right mouse click selected data to copy.
</TextBlock>
</Label>
<!-- Subnet Input -->
<Label Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="0,5,5,5">Subnet:</Label>
<TextBox x:Name="SubnetTextBox" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"
Margin="0,5,5,5" Text="Enter Subnet (192.168.1)" Width="200"/>
<Button x:Name="ScanButton" Grid.Row="1" Grid.Column="2" Margin="5" Width="75">Scan</Button>
<Button x:Name="ClearButton" Grid.Row="1" Grid.Column="3" Margin="5" Width="75">Clear</Button>
<Button x:Name="CloseButton" Grid.Row="1" Grid.Column="4" Margin="2,5,5,5" Width="75">Close</Button>
<!-- Results ListView -->
<ListView x:Name="ResultsListView" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="6"
Margin="5" BorderThickness="1" SelectionMode="Single">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy IP Address" />
<MenuItem Header="Copy Host Name" />
<MenuItem Header="Copy MAC Address" />
<MenuItem Header="Copy OS Version" />
<MenuItem Header="Copy Entire Row" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="IP Address" Width="120" DisplayMemberBinding="{Binding IPAddress}"/>
<GridViewColumn Header="Host Name" Width="200" DisplayMemberBinding="{Binding HostName}"/>
<GridViewColumn Header="MAC Address" Width="150" DisplayMemberBinding="{Binding MACAddress}"/>
<GridViewColumn Header="OS Version" Width="200" DisplayMemberBinding="{Binding OSVersion}"/>
</GridView>
</ListView.View>
</ListView>
<!-- Status Label -->
<Label x:Name="StatusTextLabel" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="6" Margin="5">
Status: Ready
</Label>
<!-- Footer -->
<Label Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="6" HorizontalAlignment="Center"
VerticalAlignment="Center" FontSize="12" FontWeight="Bold">
Developer POC - djl236
</Label>
</Grid>
</Window>
"@
#Load the XAML
[xml]$XamlObject = $Xaml
$Reader = (New-Object System.Xml.XmlNodeReader $XamlObject)
$Window = [Windows.Markup.XamlReader]::Load($Reader)
#Get UI Elements
$SubnetTextBox = $Window.FindName("SubnetTextBox")
$ScanButton = $Window.FindName("ScanButton")
$ClearButton = $Window.FindName("ClearButton")
$CloseButton = $Window.FindName("CloseButton")
$ResultsListView = $Window.FindName("ResultsListView")
$StatusTextLabel = $Window.FindName("StatusTextLabel")
#Functions
Function Copy-SelectedField
{
Param ([string]$Field)
If ($ResultsListView.SelectedItem)
{
$SelectedItem = $ResultsListView.SelectedItem
Switch ($Field)
{
"IP" { [System.Windows.Clipboard]::SetText($SelectedItem.IPAddress); Write-Host "IP Address copied." }
"HostName" { [System.Windows.Clipboard]::SetText($SelectedItem.HostName); Write-Host "Host Name copied." }
"MAC" { [System.Windows.Clipboard]::SetText($SelectedItem.MACAddress); Write-Host "MAC Address copied." }
"OS" { [System.Windows.Clipboard]::SetText($SelectedItem.OSVersion); Write-Host "OS Version copied." }
"Row" {
$RowData = "$($SelectedItem.IPAddress), $($SelectedItem.HostName), $($SelectedItem.MACAddress), $($SelectedItem.OSVersion)"
[System.Windows.Clipboard]::SetText($RowData)
Write-Host "Entire row copied."
}
}
}
Else
{
Write-Host "No item selected to copy." -ForegroundColor Yellow
}
}
#Attach Context Menu Events
$ResultsListView.ContextMenu.Items[0].Add_Click({ Copy-SelectedField -Field "IP" })
$ResultsListView.ContextMenu.Items[1].Add_Click({ Copy-SelectedField -Field "HostName" })
$ResultsListView.ContextMenu.Items[2].Add_Click({ Copy-SelectedField -Field "MAC" })
$ResultsListView.ContextMenu.Items[3].Add_Click({ Copy-SelectedField -Field "OS" })
$ResultsListView.ContextMenu.Items[4].Add_Click({ Copy-SelectedField -Field "Row" })
Function Get-MACAddress
{
Param ([string]$IPAddress)
Try
{
$LocalIP = (Get-NetIPAddress | Where-Object { $_.IPAddress -eq $IPAddress }).IPAddress
If ($LocalIP)
{
$Adapter = Get-NetIPAddress -IPAddress $IPAddress | Get-NetAdapter
If ($Adapter)
{
Return $Adapter.MacAddress
}
Else
{
Return "MAC Not Found (Local)"
}
}
$ARPTable = arp -a
$Regex = [regex]"(?<IP>\d+\.\d+\.\d+\.\d+)\s+(?<MAC>([0-9a-f]{2}-?){6})"
$Match = $Regex.Matches($ARPTable) | Where-Object { $_.Groups["IP"].Value -eq $IPAddress }
If ($Match)
{
Return $Match.Groups["MAC"].Value
}
Else
{
Return "Not Found"
}
}
Catch
{
Return "Error"
}
}
Function Get-OSVersion
{
Param ([string]$IPAddress)
Try
{
$OS = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $IPAddress -ErrorAction Stop
Return $OS.Caption
}
Catch
{
Return "Unknown or Not a Windows OS"
}
}
Function Update-Status
{
#Forces the WPF GUI to refresh immediately
$Frame = New-Object -TypeName System.Windows.Threading.DispatcherFrame
[System.Windows.Threading.Dispatcher]::CurrentDispatcher.Invoke(
[System.Action]{ $Frame.Continue = $false },
[System.Windows.Threading.DispatcherPriority]::Background
)
[System.Windows.Threading.Dispatcher]::PushFrame($Frame)
}
Function Search-Network
{
Param ([string]$Subnet)
If (-not $Subnet)
{
$StatusTextLabel.Content = "Error: Please enter a subnet."
Return
}
$StatusTextLabel.Content = "Scanning..."
#Force GUI refresh
Update-Status
$ResultsListView.Items.Clear()
Try
{
$IPRange = 1..254 | ForEach-Object { "$Subnet.$_" }
$Addresses = $IPRange -join "' or Address='"
$Filter = "(Address='$Addresses') AND StatusCode=0"
$Results = Get-WmiObject -Class Win32_PingStatus -Filter $Filter
#Manual IP Sorting
$SortedResults = $Results | Sort-Object {
$Octets = $_.Address -split '\.' | ForEach-Object { [int]$_ }
($Octets[0] * 256 * 256 * 256) + ($Octets[1] * 256 * 256) + ($Octets[2] * 256) + $Octets[3]
}
#Add Sorted Results to the ListView
ForEach ($Result in $SortedResults)
{
$IPAddress = $Result.Address
$HostName = Try { ([System.Net.Dns]::GetHostEntry($IPAddress)).HostName } Catch { "Unable to Resolve" }
$MACAddress = Get-MACAddress -IPAddress $IPAddress
$OSVersion = Get-OSVersion -IPAddress $IPAddress
If (-not $MACAddress) { $MACAddress = "Unable to Resolve" }
$Item = [PSCustomObject]@{
IPAddress = $IPAddress
HostName = $HostName
MACAddress = $MACAddress
OSVersion = $OSVersion
}
$ResultsListView.Items.Add($Item)
}
$StatusTextLabel.Content = "Scan Complete."
}
Catch
{
$StatusTextLabel.Content = "Error occurred: $_"
Write-Host "Error occurred: $_" -ForegroundColor Red
}
}
Function Clear-Results
{
$ResultsListView.Items.Clear()
$StatusTextLabel.Content = "Status: Ready"
Write-Host "Results cleared."
}
#Event Handlers
$ScanButton.Add_Click({
#Validate Subnet Format
$Subnet = $SubnetTextBox.Text
$SubnetRegex = '^\d{1,3}\.\d{1,3}\.\d{1,3}$'
If (-not [string]::IsNullOrWhiteSpace($Subnet))
{
If ($Subnet -match $SubnetRegex)
{
Search-Network -Subnet $Subnet
}
Else
{
$StatusTextLabel.Content = "Error: Invalid Subnet Format (e.g. 192.168.1)"
Write-Host "Error: Invalid Subnet Format (e.g. 192.168.1)"
}
}
Else
{
$StatusTextLabel.Content = "Error: Please enter a subnet."
Write-Host "Error: Please enter a subnet."
}
})
$ClearButton.Add_Click({ Clear-Results })
$CloseButton.Add_Click({ $Window.Close() })
#Show the Window
$Window.ShowDialog()
Comments
0 B
|👍
/👎
0 B
|👍
/👎