diff --git a/PowerShell Scanners/Certificates/Connect-Certificates.ps1 b/PowerShell Scanners/Certificates/Connect-Certificates.ps1 new file mode 100644 index 0000000..294937a --- /dev/null +++ b/PowerShell Scanners/Certificates/Connect-Certificates.ps1 @@ -0,0 +1,49 @@ +# Trimmed version for PDQ Connect. Uses a fixed set of properties rather than exposing +# -Property, which can produce very large output when set to '*'. +# Defaults to LocalMachine only, since CurrentUser stores are less relevant in a Connect context. + +[CmdletBinding()] +param ( + [ValidateSet('CurrentUser', 'LocalMachine')] + [String[]]$StoreLocation = @('LocalMachine'), + + [String[]]$StoreName +) + +$CertType = [System.Security.Cryptography.X509Certificates.X509Certificate2] + +$Properties = @( + 'FriendlyName' + 'NotBefore' + 'NotAfter' + 'Thumbprint' + 'SerialNumber' + @{ Label = 'Store'; Expression = { ($_.PSParentPath -split ':')[-1] } } +) + +foreach ( $StoreLocationIterator in $StoreLocation ) { + + if ( $StoreName ) { + + foreach ( $StoreNameIterator in $StoreName ) { + + $Param = @{ + 'Path' = "Cert:\$StoreLocationIterator\$StoreNameIterator" + 'ErrorAction' = 'SilentlyContinue' + } + Get-ChildItem @Param | Where-Object { $_ -is $CertType } | Select-Object $Properties + + } + + } else { + + $Param = @{ + 'Path' = "Cert:\$StoreLocationIterator" + 'ErrorAction' = 'SilentlyContinue' + 'Recurse' = $true + } + Get-ChildItem @Param | Where-Object { $_ -is $CertType } | Select-Object $Properties + + } + +} diff --git a/PowerShell Scanners/Certificates/README.md b/PowerShell Scanners/Certificates/README.md index c29e48d..c95577a 100644 --- a/PowerShell Scanners/Certificates/README.md +++ b/PowerShell Scanners/Certificates/README.md @@ -15,7 +15,7 @@ You may want to change the Scan As setting to `Logged on User` for this Scan Pro ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: Yes, but output needs to be trimmed +* **PDQ Connect**: Yes, see Connect-Certificates.ps1 for trimmed output ## Parameters diff --git a/PowerShell Scanners/Dell BIOS Information/Connect-Dell BIOS Information.ps1 b/PowerShell Scanners/Dell BIOS Information/Connect-Dell BIOS Information.ps1 new file mode 100644 index 0000000..4748038 --- /dev/null +++ b/PowerShell Scanners/Dell BIOS Information/Connect-Dell BIOS Information.ps1 @@ -0,0 +1,35 @@ +# Inline module installer for PDQ Connect (no relative path support) +function Install-AndImportModule { + param ( + [Parameter(Mandatory = $true)] + [String]$ModuleName + ) + try { + $null = Get-InstalledModule $ModuleName -ErrorAction Stop + } catch { + if (-not (Get-PackageProvider -ListAvailable | Where-Object Name -eq "Nuget")) { + $null = Install-PackageProvider "Nuget" -Force + } + $null = Install-Module $ModuleName -Force + } + $null = Import-Module $ModuleName -Force +} + +if (Get-CimInstance -ClassName Win32_ComputerSystem -Filter "Manufacturer LIKE '%Dell%'") { + $ModuleName = "DellBIOSProvider" + if (-not [Environment]::Is64BitOperatingSystem) { + $ModuleName += "X86" + } + Install-AndImportModule -ModuleName $ModuleName + Get-ChildItem DellSmbios:\ | ForEach-Object { + $Category = $_.Category + Get-ChildItem DellSmbios:\"$Category" -ErrorAction SilentlyContinue | ForEach-Object { + [PSCustomObject]@{ + Category = $Category + Attribute = $_.Attribute + Description = $_.Description + CurrentValue = $_.CurrentValue + } + } + } +} diff --git a/PowerShell Scanners/Dell BIOS Information/README.md b/PowerShell Scanners/Dell BIOS Information/README.md index f1666a1..68a7238 100644 --- a/PowerShell Scanners/Dell BIOS Information/README.md +++ b/PowerShell Scanners/Dell BIOS Information/README.md @@ -11,7 +11,7 @@ Uses DellBIOSProvider module to gather current BIOS settings. Script is not setu ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: No +* **PDQ Connect**: Yes, see Connect-Dell BIOS Information.ps1 ## Author diff --git a/PowerShell Scanners/File Hash/Connect-File Hash.ps1 b/PowerShell Scanners/File Hash/Connect-File Hash.ps1 new file mode 100644 index 0000000..73a7f5a --- /dev/null +++ b/PowerShell Scanners/File Hash/Connect-File Hash.ps1 @@ -0,0 +1,20 @@ +param ( + # Path is required in Connect: $env:userprofile resolves to the SYSTEM profile when run as SYSTEM. + [Parameter(Mandatory = $true)] + [string]$Path, + [string]$Filter, + [switch]$Recurse, + [string]$Algorithm = "SHA256" +) + +$Files = Get-ChildItem -File -Path $Path -Filter $Filter -Recurse:$Recurse + +ForEach ( $File in $Files ) { + + [PSCustomObject]@{ + Name = $File.Name + Path = $File.FullName + Hash = (Get-FileHash $File.PSPath -Algorithm $Algorithm).Hash + } + +} diff --git a/PowerShell Scanners/File Hash/README.md b/PowerShell Scanners/File Hash/README.md index 2a27b74..770a713 100644 --- a/PowerShell Scanners/File Hash/README.md +++ b/PowerShell Scanners/File Hash/README.md @@ -11,7 +11,7 @@ Runs `Get-FileHash` on files in a directory to calculate their hash values ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: No - different version needed +* **PDQ Connect**: Yes, see Connect-File Hash.ps1 ## Parameters diff --git a/PowerShell Scanners/Get Available Windows Updates/Connect-Get Available Windows Updates.ps1 b/PowerShell Scanners/Get Available Windows Updates/Connect-Get Available Windows Updates.ps1 new file mode 100644 index 0000000..dabf932 --- /dev/null +++ b/PowerShell Scanners/Get Available Windows Updates/Connect-Get Available Windows Updates.ps1 @@ -0,0 +1,129 @@ +[CmdletBinding()] +Param( + [switch]$WSUS +) + +# Inline module installer for PDQ Connect (no relative path support) +function Install-AndImportModule { + param ( + [Parameter(Mandatory = $true)] + [String]$ModuleName + ) + try { + $null = Get-InstalledModule $ModuleName -ErrorAction Stop + } catch { + if (-not (Get-PackageProvider -ListAvailable | Where-Object Name -eq "Nuget")) { + $null = Install-PackageProvider "Nuget" -Force + } + $null = Install-Module $ModuleName -Force + } + $null = Import-Module $ModuleName -Force +} + +Install-AndImportModule -ModuleName "PSWindowsUpdate" + +# The Collection object this cmdlet emits is really weird. +# We have to assign it to a variable to get it to work properly in a pipeline. +If ($WSUS) { + # No service flag: the Windows Update Agent uses the service configured by GPO (i.e. WSUS). + # -WindowsUpdate forces the online WU service ID and bypasses WSUS filtering. + $GWU = Get-WindowsUpdate +} +Else { + # -MicrosoftUpdate queries Microsoft's catalog directly, returning all available updates + # regardless of what the machine's WSUS policy would approve. + $GWU = Get-WindowsUpdate -MicrosoftUpdate +} + +<# +Due to a bug in Inventory, we want this script to always emit an object, even if no updates were found. + +When you set a variable equal to the output of a cmdlet and that cmdlet returns nothing, your variable +will contain an empty PSCustomObject. +ForEach-Object skips empty PSCustomObjects, but not objects that are truly empty (such as $null). +Clear-Variable sets a variable back to a truly empty object, which causes this script to emit the +PSCustomObject we build below with all of the values of its properties set to $null instead of skipping it. + +Here's some code you can use to test this yourself: +$GWU = foreach ( $Thing in $null ) {} +$GWU.PSObject +$GWU | ForEach-Object { [PSCustomObject]@{ 'KB' = $_.KB } } + +Clear-Variable 'GWU' +$GWU.PSObject +$GWU | ForEach-Object { [PSCustomObject]@{ 'KB' = $_.KB } } +#> +If ( $null -eq $GWU ) { + + Clear-Variable 'GWU' + +} + +$GWU | ForEach-Object { + [PSCustomObject]@{ + "KB" = $_.KB + "Title" = $_.Title + + # Size is a string that represents a quantity of bytes as a numeric literal. For example, '321MB'. + # Converting it to an integer lets Inventory recognize and handle it properly. + # https://stackoverflow.com/a/41091259 + "Size" = [UInt64]($_.Size / 1) + + "Status" = $_.Status + "Description" = $_.Description + "RebootRequired" = $_.RebootRequired + + # Indicates whether the update is flagged to be automatically selected by Windows Update. + "AutoSelectOnWebSites" = $_.AutoSelectOnWebSites + + "CanRequireSource" = $_.CanRequireSource + "Deadline" = $_.Deadline + "DeltaCompressedContentAvailable" = $_.DeltaCompressedContentAvailable + "DeltaCompressedContentPreferred" = $_.DeltaCompressedContentPreferred + "EulaAccepted" = $_.EulaAccepted + "IsBeta" = $_.IsBeta + "IsDownloaded" = $_.IsDownloaded + "IsHidden" = $_.IsHidden + "IsInstalled" = $_.IsInstalled + "IsMandatory" = $_.IsMandatory + "IsPresent" = $_.IsPresent + "IsUninstallable" = $_.IsUninstallable + "UninstallationNotes" = $_.UninstallationNotes + "LastDeploymentChangeTime" = $_.LastDeploymentChangeTime + + # Convert Decimal to 64-bit Integer + "MaxDownloadSize" = [UInt64]$_.MaxDownloadSize + "MinDownloadSize" = [UInt64]$_.MinDownloadSize + + "MsrcSeverity" = $_.MsrcSeverity + + # MHz + "RecommendedCpuSpeed" = $_.RecommendedCpuSpeed + + # https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdate-get_recommendedharddiskspace + "RecommendedHardDiskSpace" = [UInt64]($_.RecommendedHardDiskSpace * 1MB) + + # https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdate-get_recommendedmemory + "RecommendedMemorySize" = [UInt64]($_.RecommendedMemory * 1MB) + + "ReleaseNotes" = $_.ReleaseNotes + "SupportUrl" = $_.SupportUrl + + # https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-updatetype + "Type" = switch ($_.Type) { + 1 { "Software" } + 2 { "Driver" } + Default { "Unknown" } + } + + "DeploymentAction" = $_.DeploymentAction + "DownloadPriority" = $_.DownloadPriority + + # Indicates whether an update can be discovered only by browsing through the available updates. + "BrowseOnly" = $_.BrowseOnly + + "PerUser" = $_.PerUser + "AutoSelection" = $_.AutoSelection + "AutoDownload" = $_.AutoDownload + } +} diff --git a/PowerShell Scanners/Get Available Windows Updates/README.md b/PowerShell Scanners/Get Available Windows Updates/README.md index b6bf43c..0b62f33 100644 --- a/PowerShell Scanners/Get Available Windows Updates/README.md +++ b/PowerShell Scanners/Get Available Windows Updates/README.md @@ -11,7 +11,7 @@ Installs the PSWindowsUpdate module, then runs Get-WindowsUpdate. This returns a ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: No, working on a version that does +* **PDQ Connect**: Yes, see Connect-Get Available Windows Updates.ps1 ## Parameters diff --git a/PowerShell Scanners/Get CDP Neighbor Data/Connect-Get CDP Neighbor Data.ps1 b/PowerShell Scanners/Get CDP Neighbor Data/Connect-Get CDP Neighbor Data.ps1 new file mode 100644 index 0000000..7887f63 --- /dev/null +++ b/PowerShell Scanners/Get CDP Neighbor Data/Connect-Get CDP Neighbor Data.ps1 @@ -0,0 +1,64 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory = $false)] + [int]$PacketWaitDuration +) + +# Inline module installer for PDQ Connect (no relative path support) +function Install-AndImportModule { + param ( + [Parameter(Mandatory = $true)] + [String]$ModuleName + ) + try { + $null = Get-InstalledModule $ModuleName -ErrorAction Stop + } catch { + if (-not (Get-PackageProvider -ListAvailable | Where-Object Name -eq "Nuget")) { + $null = Install-PackageProvider "Nuget" -Force + } + $null = Install-Module $ModuleName -Force + } + $null = Import-Module $ModuleName -Force +} + +Install-AndImportModule -ModuleName "PSDiscoveryProtocol" + +if ( $PacketWaitDuration ) { + $CDPPacket = Invoke-DiscoveryProtocolCapture -Type CDP -Duration $PacketWaitDuration -Force +} else { + $CDPPacket = Invoke-DiscoveryProtocolCapture -Type CDP -Force +} + +if ( $CDPPacket ) { + $Results = Get-DiscoveryProtocolData -Packet $CDPPacket + if ( $Results ) { + ForEach ( $Result in $Results ) { + [PSCustomObject]@{ + "NeighborDeviceName" = $Result.Device + "NeighborDevicePort" = $Result.Port + "NeighborDeviceIP" = $Result.IPAddress[0] + "LocalInterface" = $Result.Interface + "VLAN" = $Result.VLAN + "AsOf" = Get-Date + } + } + } else { + [PSCustomObject]@{ + "NeighborDeviceName" = "Parsing Error" + "NeighborDevicePort" = "Parsing Error" + "NeighborDeviceIP" = "Parsing Error" + "LocalInterface" = "Parsing Error" + "VLAN" = "Parsing Error" + "AsOf" = Get-Date + } + } +} else { + [PSCustomObject]@{ + "NeighborDeviceName" = "No CDP Packet Received" + "NeighborDevicePort" = "No CDP Packet Received" + "NeighborDeviceIP" = "No CDP Packet Received" + "LocalInterface" = "No CDP Packet Received" + "VLAN" = "No CDP Packet Received" + "AsOf" = Get-Date + } +} diff --git a/PowerShell Scanners/Get CDP Neighbor Data/README.md b/PowerShell Scanners/Get CDP Neighbor Data/README.md index 837efb1..8b8f936 100644 --- a/PowerShell Scanners/Get CDP Neighbor Data/README.md +++ b/PowerShell Scanners/Get CDP Neighbor Data/README.md @@ -15,7 +15,7 @@ All files are present in this folder. Network equipment has to send CDP packets ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: No, but it's possible +* **PDQ Connect**: Yes, see Connect-Get CDP Neighbor Data.ps1 ## Parameters diff --git a/PowerShell Scanners/Get Windows Update History/Connect-Get Windows Update History.ps1 b/PowerShell Scanners/Get Windows Update History/Connect-Get Windows Update History.ps1 new file mode 100644 index 0000000..1ae0309 --- /dev/null +++ b/PowerShell Scanners/Get Windows Update History/Connect-Get Windows Update History.ps1 @@ -0,0 +1,27 @@ +param ( + # PDQ Inventory reads this from Scanner.log; in Connect use a parameter with a sensible default. + [Int32]$Limit = 200 +) + +# Inline module installer for PDQ Connect (no relative path support) +function Install-AndImportModule { + param ( + [Parameter(Mandatory = $true)] + [String]$ModuleName + ) + try { + $null = Get-InstalledModule $ModuleName -ErrorAction Stop + } catch { + if (-not (Get-PackageProvider -ListAvailable | Where-Object Name -eq "Nuget")) { + $null = Install-PackageProvider "Nuget" -Force + } + $null = Install-Module $ModuleName -Force + } + $null = Import-Module $ModuleName -Force +} + +Install-AndImportModule -ModuleName "PSWindowsUpdate" + +# -Last limits the number of results. This is necessary in Windows 10 2004 and later. +# https://github.com/pdq/PowerShell-Scanners/issues/74 +Get-WUHistory -Last $Limit diff --git a/PowerShell Scanners/Get Windows Update History/README.md b/PowerShell Scanners/Get Windows Update History/README.md index bb60c82..df0422f 100644 --- a/PowerShell Scanners/Get Windows Update History/README.md +++ b/PowerShell Scanners/Get Windows Update History/README.md @@ -11,7 +11,7 @@ Installs the PSWindowsUpdate module, then runs Get-WUHistory. This returns the c ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: No, but i think it's possible +* **PDQ Connect**: Yes, see Connect-Get Windows Update History.ps1 ## Author diff --git a/PowerShell Scanners/Hosts File/Connect-Hosts File.ps1 b/PowerShell Scanners/Hosts File/Connect-Hosts File.ps1 new file mode 100644 index 0000000..c7e7534 --- /dev/null +++ b/PowerShell Scanners/Hosts File/Connect-Hosts File.ps1 @@ -0,0 +1,92 @@ +# PowerShell 7 compatible version for PDQ Connect. +# The Inventory version uses ConvertFrom-String which is not available in PowerShell 7+. +# This version parses the same data using basic string splitting. + +[CmdletBinding()] +param ( + [Switch]$ShowDisabled +) + +$FileContents = (Get-Content "$env:SystemRoot\System32\drivers\etc\hosts").Trim() | Where-Object { $_ } + +$Count = 0 +$hostsinfile = $null + +Foreach ( $Line in $FileContents ) { + + $OriginalLine = $Line + $Enabled = $true + $Count ++ + + if ( $Line.StartsWith('#') ) { + + if ( $ShowDisabled ) { + + $Enabled = $false + $Line = $Line.TrimStart('# ') + + } else { + + Write-Verbose "Line #$Count is a comment and ShowDisabled is not active." + Continue + + } + + } + + # Strip trailing inline comment. + $Line, $Comments = ($Line -split '#', 2).Trim() + + if ( -not $Line ) { + + Write-Verbose "Line #$Count is an empty comment." + Continue + + } + + # Split on whitespace into tokens. + $Tokens = $Line -split '\s+' | Where-Object { $_ } + + if ( $Tokens.Count -lt 2 ) { + + if ( $Enabled ) { + Write-Error "Malformed line: '$OriginalLine'" + } + Write-Verbose "Line #$Count could not be split." + Continue + + } + + $IPAddress = $Tokens[0] + + # Validate IP address. + try { + $null = [ipaddress]$IPAddress + } catch { + Write-Verbose "Line #$Count does not start with an IP address." + Continue + } + + # Output one object per hostname on the line. + foreach ( $HostName in $Tokens[1..($Tokens.Count - 1)] ) { + + $hostsinfile = [PSCustomObject]@{ + 'HostName' = $HostName + 'IPAddress' = $IPAddress + 'Enabled' = $Enabled + 'Comments' = $Comments + } + $hostsinfile + + } + +} + +if ( $null -eq $hostsinfile ) { + [PSCustomObject]@{ + 'HostName' = $null + 'IPAddress' = $null + 'Enabled' = $false + 'Comments' = $null + } +} diff --git a/PowerShell Scanners/Hosts File/README.md b/PowerShell Scanners/Hosts File/README.md index 115b7a5..23e0e88 100644 --- a/PowerShell Scanners/Hosts File/README.md +++ b/PowerShell Scanners/Hosts File/README.md @@ -26,7 +26,7 @@ it.crowd | 3.4.5.6 | False | I'm disabled. ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: Yes, check more first +* **PDQ Connect**: Yes, see Connect-Hosts File.ps1 for PowerShell 7 compatibility ## Parameters diff --git a/PowerShell Scanners/Windows Update Last Installed/Connect-Windows Update Last Installed.ps1 b/PowerShell Scanners/Windows Update Last Installed/Connect-Windows Update Last Installed.ps1 new file mode 100644 index 0000000..8418b7d --- /dev/null +++ b/PowerShell Scanners/Windows Update Last Installed/Connect-Windows Update Last Installed.ps1 @@ -0,0 +1,26 @@ +# Inline module installer for PDQ Connect (no relative path support) +function Install-AndImportModule { + param ( + [Parameter(Mandatory = $true)] + [String]$ModuleName + ) + try { + $null = Get-InstalledModule $ModuleName -ErrorAction Stop + } catch { + if (-not (Get-PackageProvider -ListAvailable | Where-Object Name -eq "Nuget")) { + $null = Install-PackageProvider "Nuget" -Force + } + $null = Install-Module $ModuleName -Force + } + $null = Import-Module $ModuleName -Force +} + +Install-AndImportModule -ModuleName "PSWindowsUpdate" + +$lastResults = Get-WULastResults + +[PSCustomObject]@{ + LastInstallationDate = [DateTime] $lastResults.LastInstallationSuccessDate + LastScanSuccessDate = [DateTime] $lastResults.LastSearchSuccessDate + IsPendingReboot = [Bool] (Get-WURebootStatus -Silent) +} diff --git a/PowerShell Scanners/Windows Update Last Installed/README.md b/PowerShell Scanners/Windows Update Last Installed/README.md index a817352..e6e8968 100644 --- a/PowerShell Scanners/Windows Update Last Installed/README.md +++ b/PowerShell Scanners/Windows Update Last Installed/README.md @@ -13,7 +13,7 @@ Installs the PSWindowsUpdate module, then runs the following cmdlets: ## Compatibility * **PDQ Inventory**: Yes -* **PDQ Connect**: No, but maybe possible +* **PDQ Connect**: Yes, see Connect-Windows Update Last Installed.ps1 ## Author