<# **************************************************************************************** * 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 = @" "@ #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]"(?\d+\.\d+\.\d+\.\d+)\s+(?([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()