# windowsDiscovery.ps1
function checkSpectreVulnerability($computer=$env:computername){
$command={
$patchedVersion="10.0.14393.2842"
$actualVersion=(Get-Item C:\Windows\system32\mcupdate_genuineintel.dll | select VersionInfo).VersionInfo.ProductVersion
$intelDllPatched=[version]$actualVersion -ge [version]$patchedVersion
<#
if(!$intelDllPatched){
write-warning "mcupdate_genuineintel.dll $actualVersion IS VULNERABLE to Spectre meltdown"
}else{
write-host "mcupdate_genuineintel.dll $actualVersion is NOT vulnerable to Spectre meltdown" -ForegroundColor Green
}
#>
# Source: https://support.microsoft.com/en-us/topic/kb4073119-windows-client-guidance-for-it-pros-to-protect-against-silicon-based-microarchitectural-and-speculative-execution-side-channel-vulnerabilities-35820a8a-ae13-1299-88cc-357f104f5b11
# If Hyper-V feature is enabled
$virtualizationRegKey='REGISTRY::HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization'
$virtualization=try{get-itemproperty $virtualizationRegKey}catch{$false}
$virtualServerMitigated=if($virtualization){
$virtualServerMitigated=[int]($virtualization.MinVmVersionForCpuBasedMitigations) -ge 1
if($virtualServerMitigated){
#write-host "Hyper-V server $env:computername has enabled MinVmVersionForCpuBasedMitigations" -ForegroundColor Green
$true
}else{
#write-warning "Hyper-V server $env:computername has NOT enabled MinVmVersionForCpuBasedMitigations"
$false
}
#reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization" /v MinVmVersionForCpuBasedMitigations /t REG_SZ /d "1.0" /f
}else{
'N/A'
}
# To enable mitigations for:
# CVE-2017-5715 (Spectre Variant 2)
# CVE-2017-5754 (Meltdown)
# CVE-2018-3639 (Speculative Store Bypass)
$spectreMeltDownRegKey='REGISTRY::HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management'
#reg add $spectreMeltDownRegKey /v FeatureSettingsOverride /t REG_DWORD /d 8 /f #disable: 3
#reg add $spectreMeltDownRegKey /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f
# To enable mitigations for: CVE-2017-5715 (Spectre Variant 2)
#$branchTargetInjectionRegKey="REGISTRY::HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management"
#reg add $branchTargetInjectionRegKey /v FeatureSettingsOverride /t REG_DWORD /d 0 /f #disable: 1
#reg add $branchTargetInjectionRegKey /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f
$featureSettingsOverrideValue=(Get-ItemProperty $spectreMeltDownRegKey).FeatureSettingsOverride
$mitigationStatus=switch($featureSettingsOverrideValue){
0 {'ENABLED: default mitigations for CVE-2017-5715 (Spectre Variant 2) and CVE-2017-5754 (Meltdown)'}
1 {'DISABLED: mitigations for CVE-2017-5715 (Spectre Variant 2)'}
3 {'DISABLED: all Spectre mitigations'}
8 {'ENABLED: mitigations for CVE-2018-3639 (Speculative Store Bypass), default mitigations for CVE-2017-5715 (Spectre Variant 2) and CVE-2017-5754 (Meltdown)'}
64 {'ENABLED: user-to-kernel protection on AMD and ARM processors together with other protections for CVE 2017-5715'}
72 {'ENABLED: user-to-kernel protection on AMD processors together with other protections for CVE 2017-5715 and protections for CVE-2018-3639 (Speculative Store Bypass)`r`nmitigations for Intel® Transactional Synchronization Extensions (Intel® TSX) Transaction Asynchronous Abort vulnerability (CVE-2019-11135) and Microarchitectural Data Sampling (CVE-2018-11091, CVE-2018-12126, CVE-2018-12127, CVE-2018-12130) along with Spectre (CVE-2017-5753 & CVE-2017-5715) and Meltdown (CVE-2017-5754) variants, including Speculative Store Bypass Disable (SSBD) (CVE-2018-3639) as well as L1 Terminal Fault (L1TF) (CVE-2018-3615, CVE-2018-3620, and CVE-2018-3646) without disabling Hyper-Threading'}
8264 {'ENABLED: mitigations for Intel® Transactional Synchronization Extensions (Intel® TSX) Transaction Asynchronous Abort vulnerability (CVE-2019-11135) and Microarchitectural Data Sampling (CVE-2018-11091, CVE-2018-12126, CVE-2018-12127, CVE-2018-12130) along with Spectre (CVE-2017-5753 & CVE-2017-5715) and Meltdown (CVE-2017-5754) variants, including Speculative Store Bypass Disable (SSBD) (CVE-2018-3639) as well as L1 Terminal Fault (L1TF) (CVE-2018-3615, CVE-2018-3620, and CVE-2018-3646) with Hyper-Threading disabled'}
}
return [PSCustomObject][ordered]@{
computerName=$env:computername
intelDllPatched = $intelDllPatched
vmSpectreMitigationEnabled = $virtualServerMitigated
osMitigationSettings = if($mitigationStatus){$mitigationStatus}else{'None'}
}
}
invoke-command -ComputerName $computer -ScriptBlock $command|select -Property * -ExcludeProperty RunspaceId
}
function checkSpeculationControls($computer=$env:computername){
$command={
# Interpretations of output: https://support.microsoft.com/en-us/topic/kb4074629-understanding-speculationcontrol-powershell-script-output-fd70a80a-a63f-e539-cda5-5be4c9e67c04
#$originalExecutionPolicy = Get-ExecutionPolicy
#Set-ExecutionPolicy RemoteSigned -Scope Currentuser
$ErrorActionPreference='stop'
if(!(Get-command Get-SpeculationControlSettings -ea SilentlyContinue)){
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
if(!(Get-PackageProvider 'nuget' -ea SilentlyContinue)){
try{
Install-PackageProvider -Name 'NuGet' -Force
}catch{
write-warning "$env:computername is unable to reach go.microsoft.com to obtain NuGet."
#Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
#Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
#Install-PackageProvider -Name NuGet -Force
return $false
}
# Overcome this error:
#WARNING: Unable to download from URI 'https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409' to ''.
#WARNING: Unable to download the list of available providers. Check your internet connection.
#Install-PackageProvider : No match was found for the specified search criteria for the provider 'NuGet'. The package
#provider requires 'PackageManagement' and 'Provider' tags. Please check if the specified package has the tags.
# + CategoryInfo : InvalidArgument: (Microsoft.Power...PackageProvider:InstallPackageProvider) [Install-PackageProvider], Exception
# + FullyQualifiedErrorId : NoMatchFoundForProvider,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackageProvider
}
try{
Install-Module SpeculationControl -force
}catch{
write-warning $_
return $false
}
}
#Set-ExecutionPolicy $originalExecutionPolicy -Scope Currentuser
$speculationControlSettings=Get-SpeculationControlSettings -Quiet
$speculationControlSettings|Add-Member -MemberType NoteProperty -name 'computerName' -value $env:computername
return $speculationControlSettings
}
invoke-command -ComputerName $computer -ScriptBlock $command
}
function checkBios($computer=$env:computername){
if($env:computername -ne $computer){
$thisSession=new-pssession $computer
}
invoke-command -computername $computer -scriptblock{
function displayArrayAsColumns($array){
function inflateString([string]$string="test",[int]$maxInflate=6,[string]$fillWith=' '){
[int]$difference=$maxInflate-$string.tostring().Length
if($difference -gt 0){
[string]$fill=$fillWith*$difference
return $string+$fill
}
}
$columnWidth=.{return ($array[$array.length-1]).tostring().Length+1}
$numberOfElements=$array.Count
$numberOfRows=[math]::Ceiling(($numberOfElements+$columnWidth)/4)
$splittedArray=@()
$fragment=[math]::Ceiling($numberOfElements/$numberOfRows)
$array=$array|%{inflateString $_ $columnWidth}
for ($i=0;$i -lt $numberOfRows; $i++){
$endIndex=($fragment*($i+1))-1;
$startIndex=$endIndex-$fragment+1;
if($i -eq $columns-1){$endIndex=$elementsCount-1}
#write-host "startIndex: $startIndex | endIndex: $endIndex";
$splittedArray+=,$array[$startIndex..$endIndex];
}
return ($splittedArray|%{$_ -join ''} | out-string).trim()
}
$biosRegKey="REGISTRY::HKEY_LOCAL_MACHINE\Hardware\Description\System\Bios"
$bios=Get-ItemProperty $biosRegKey
$computerInfo=Get-ComputerInfo
$cpuVirtualizationEnabledInBios=if($computerInfo.HyperVRequirementVirtualizationFirmwareEnabled){$true}else{$false}
$os=$computerInfo.OsName
$osHotFixes=$computerInfo.OsHotFixes.HotFixID
$cpuModel=($computerInfo.CsProcessors|Select-Object -Unique).Name
$cpuCount=$computerInfo.CsNumberOfProcessors
$cpuUtilization=(Get-WmiObject win32_processor|Measure-Object -property LoadPercentage -Average|Select @{Name="CurrentLoad";Expression={"{0:N2} %" -f ($_.Average)}}).CurrentLoad
$logicalCpuCount=$computerInfo.CsNumberOfLogicalProcessors
$memoryGb=[math]::round($computerInfo.OsTotalVisibleMemorySize/1048576)
$osInfo=gwmi -Class win32_operatingsystem
$memoryUtilization="{0:N2} %" -f ((($osInfo.TotalVisibleMemorySize - $osInfo.FreePhysicalMemory)*100)/ $osInfo.TotalVisibleMemorySize)
return [PSCustomObject][ordered]@{
computerName=$env:computername
manufacturer=($bios.BaseBoardManufacturer).split(' ')[0]
model=$bios.SystemProductName
motherboard=$bios.BaseBoardProduct
motherboardVersion=$bios.BaseBoardVersion
biosVersion=$bios.BIOSVersion
biosReleaseDate=$bios.BIOSReleaseDate
cpuVirtualizationEnabledInBios=$cpuVirtualizationEnabledInBios
os=$os
osHotFixes=displayArrayAsColumns $osHotFixes
cpuModel=$cpuModel
cpuCount=$cpuCount
logicalCpuCount=$logicalCpuCount
cpuUtilization=$cpuUtilization
memoryGb=$memoryGb
memoryUtilization=$memoryUtilization
}
#$manufacturer=$bios.BaseBoardManufacturer
#$wmiBios=Get-WmiObject -computername $computer win32_bios
#$systemInfoBios=systeminfo | findstr /I /c:bios #This gives us the date as well
#$biosVersion=$wmiBios.SMBIOSBIOSVersion
}
}
function checkNetwork($computer=$env:computername){
invoke-command -ComputerName $computer -ScriptBlock{
$adapters=get-netadapter|?{$_.Status -eq 'Up'}|select Name,InterfaceDescription,LinkSpeed
$physicalAdapters=get-netadapter -physical|?{$_.Status -eq 'Up'}|select Name,InterfaceDescription,LinkSpeed,@{Name='vmq';e={$vmqEnabled=try{(Get-NetAdapterVmq $_.Name).Enabled}catch{$false};if($vmqEnabled){'VMQ Enabled'}else{'VMQ Disabled'}}}
$virtualAdapters=($adapters|?{$_.Name -notin $physicalAdapters.Name}).Name
$netOffloadSettings=Get-NetOffloadGlobalSetting
return [pscustomobject][ordered]@{
computerName=$env:computerName
physicalAdapters=($physicalAdapters|Sort-Object|ft -HideTableHeaders|out-string).trim()
virtualAdapters=if($virtualAdapters){($virtualAdapters|Sort-Object|out-string).trim()}else{'None'}
netOffloadSettings=($netOffloadSettings|out-string).trim()
}
}
}
function checkHyperVConfigs($computer=$env:computername){
$getHyperVConfigs={
$offlineFileCache=(Get-WmiObject win32_offlinefilescache).Enabled
$volumes=gwmi -Class win32_volume -Filter "DriveType!=5" -ea stop| ?{$_.DriveLetter -ne $isnull}|Select-object @{Name="Letter";Expression={$_.DriveLetter}},@{Name="Label";Expression={$_.Label}},@{Name="Capacity";Expression={"{0:N2}GB" -f ($_.Capacity/1073741824)}},@{Name = "Utilization"; Expression = {"{0:N2} %" -f ((($_.Capacity-$_.FreeSpace) / $_.Capacity)*100)}}
return [PSCustomObject][ordered]@{
computerName=$env:computername
offlineFileCache=$offlineFileCache
volumes=($volumes|Sort-Object|ft -HideTableHeaders|out-string).trim() -replace '\s{2}',''
}
}
invoke-command -ComputerName $computer -ScriptBlock $getHyperVConfigs
}
function checkSecurity($computer=$env:computername){
$securityCheckCommand={
$remoteCodeExecutionScan=.{
$passVersion='14.0'
$visualCFiles=(Get-ItemProperty Registry::HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue|?{$_.displayname -like "Microsoft Visual C++*"}|select DisplayName,DisplayVersion)
$results=@()
foreach ($vc in $visualCFiles){
$displayName=$vc.DisplayName
[version]$displayVersion=$vc.DisplayVersion
if($displayVersion -ge [version]$passVersion){
$results+="$displayName`: Pass"
}else{
$results+="$displayName`: Fail"
}
}
return $results
}
$unquotedServiceEnumerationPassed=.{
$unquotedServicePathItems=(wmic service get name","displayname","pathname","startmode |findstr /i "auto" |findstr /i /v "c:\windows\\" |findstr /i /v "''").Trim()
if ($unquotedServicePathItems){
return $false
}else{
return $true
}
}
$rdpSecurityPassed=.{
$rdpAuth=(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").UserAuthenticationRequired
#$encryptionLevel=(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").MinEncryptionLevel
#$compliant=switch ($encryptionLevel){
# 1 {"Low";}
# 2 {"Client Compatible";}
# 3 {"High";}
# 4 {"FIPS Compliant";}
#}
if($rdpAuth){
#write-host "RDP Network Authentication Requirement: passed!`nRDP Encryption Level: $compliant"
return $true
}else{
#write-host "RDP Network Authentication Requirement: Fail";
return $false
}
}
$ieSecurityScan=.{
$ieKeys=@(
@("CVE-2017-829 (32-Bit)","HKLM:SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX"),
@("CVE-2017-8529 (64-bit)","HKLM:SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX"),
@("ASLR Hardening Setting for IE (32-Bit)","HKLM:SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING"),
@("ASLR Hardening Setting for IE (64-Bit)","HKLM:SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING")
)
foreach ($ieKey in $ieKeys){
try{
$value=(Get-ItemProperty -Path $ieKey[1] -Name "iexplore.exe" -ErrorAction SilentlyContinue).'iexplore.exe';
}catch{
$value=0;
continue;
}
$ieResult=if($value){"Pass"}else{"Fail";}
$ieKey[0] + ": " + $ieResult
}
}
$memoryManagementScan=.{
$memKeys=@(
@("CVE-2017-5715","HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management","FeatureSettingsOverride","0"),
@("CVE-2017-5715","HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management","FeatureSettingsOverrideMask","3"),
@("CVE-2017-5753-54","HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization","MinVmVersionForCpuBasedMitigations","1.0")
)
foreach ($memKey in $memKeys){
$value=(Get-ItemProperty -Path $memKey[1] -Name $memKey[2] -ErrorAction SilentlyContinue).[string]($memKey[2])
$memResult=if($value -eq $memKey[3]){"Pass"}else{"Fail";}
$memKey[0]+ ": " + $memResult;
}
}
$localAdmins=.{
net localgroup administrators | where {$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4
}
$scheduledTasksRunasDomainAccount=.{
$domain="$env:USERDOMAIN"
$Results = @() #Initializes an empty array
ForEach ($Computer in $ComputerNames){
# Use the legacy schtasks command from localhost to query remote machine and format an output int CSV format
$tasksAsCSV = schtasks.exe /query /s $env:computername /V /FO CSV
# Process the CSV result into PowerShell. Filter entries that are not labeled as "TaskName" and by "Run as User" field
$result = $tasksAsCSV | ConvertFrom-Csv | ? { $_.TaskName -ne "TaskName" -and $_."Run As User" -match $domain}
$results += $result
}
if ($Results){
Return $results."Task To Run"
}else{
'None'
}
}
$servicesRunasDomainAccount=.{
$domain="$env:USERDOMAIN"
$services=Get-Wmiobject win32_service|select-object Name,StartName|?{$_.StartName -match $domain}
if($services){
return $services
}else{
return 'None'
}
}
return [pscustomobject][ordered]@{
computerName=$env:computername
rdpSecurityPassed=$rdpSecurityPassed
ieSecurity=($ieSecurityScan|out-string).trim()
remoteCodeExecution=($remoteCodeExecutionScan|out-string).trim()
memoryManagement=($memoryManagementScan|out-string).trim()
unquotedServiceEnumerationPassed=$unquotedServiceEnumerationPassed
localAdmins=($localAdmins|out-string).trim()
servicesRunasDomainAccount=$servicesRunasDomainAccount
scheduledTasksRunasDomainAccount=$scheduledTasksRunasDomainAccount
}
}
invoke-command -ComputerName $computer -ScriptBlock $securityCheckCommand
}
function checkInstalledApps($computer=$env:computername){
$getInstalledApps={
$installedApps=Get-CimInstance -computername $computer -ClassName win32_InstalledWin32Program -ErrorAction Stop|select Name,Version
return [PSCustomObject][ordered]@{
computerName=$env:computername
installedApps=($installedApps|Sort-Object|ft -HideTableHeaders|out-string).trim() -replace '\s{2}',''
}
}
invoke-command -ComputerName $computer -ScriptBlock $getInstalledApps
}
function checkIis($computer=$env:computername){
$checkIis={
$iis=Get-WindowsOptionalFeature -Online -FeatureName 'IIS-WebServer'
$webSites=if($iis.State -eq 'Enabled'){
get-website
}else{
'None'
}
$ftpInstalled=Get-WindowsFeature Web-Ftp-Server|Select -Expand Installed
return [PSCustomObject][ordered]@{
computerName=$env:computername
iisStatus=$iis.State
webSites=($webSites|ft|out-string).trim()
ftpInstalled=$ftpInstalled
}
}
invoke-command -ComputerName $computer -ScriptBlock $checkIis
}
function checkDotNet($computer=$env:computername){
$checkDotNet={
$dotNet=Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse| `
Get-ItemProperty -name Version,Release -EA 0|?{$_.PSChildName -match '^(?!S)\p{L}'}| `
Select-Object @{name='dotNetProduct';e={$_.PSChildName}}, Version, Release
return [PSCustomObject][ordered]@{
computerName=$env:computername
iisStatus=$iis.State
dotNetProducts=($dotNet|ft|out-string).trim()
}
}
invoke-command -ComputerName $computer -ScriptBlock $checkDotNet
}
function checkConnections($computer=$env:computername){
function invokeGetProcessPorts{
[cmdletbinding()]
Param(
[string[]]$computerNames=$env:computername,
[string[]]$processNames=$null,
[string[]]$portNumbers=$null
)
function getProcessConnections{
[cmdletbinding()]
Param(
[parameter(ValueFromPipeLine=$True)][AllowEmptyCollection()][string[]]$processNames=$null,
[parameter(ValueFromPipeLine=$True)][AllowEmptyCollection()][string[]]$portNumbers=$null
)
# Initialize variables
$results=@()
$psVersionFeasible=$PSVersionTable.PSVersion.Major -ge 4
$processes=if($processNames){
write-host "Checking connections on for process name(s): $processNames..."
if($psVersionFeasible){
try{
get-process $processNames -IncludeUserName # This is only available in PoSh 4.0+
}catch{
write-warning $_
}
}else{
get-process $processNames
}
}else{
if($psVersionFeasible){
get-process -IncludeUserName
}else{
get-process
}
}
if($processes){
write-host "Checking $($processes.count) processes..." -ForegroundColor Yellow
# Collecting wmiobjects to be able to invoke .getowner() method
$processObjects=if(!$psVersionFeasible){Get-WmiObject Win32_Process}else{$null}
$netStat=if($portNumbers){
write-host "Checking connections on port(s) $portNumbers..."
netstat -ano|?{$_ -match "\:($($portNumbers -join '|'))\s"}
}else{
write-host 'Port numbers were not defined. Scanning all ports...' -ForegroundColor Yellow
Netstat -ano|?{$_ -match "\d$"}
}
$previousPid=$previousOwner=$null
write-verbose "Process ID:"
foreach($process in $processes){
$processId=$process.ID
write-verbose "$processId"
$processName=$process.ProcessName
$processOwner=if($processId -ne $previousPid){
$previousOwner=if($processObjects){
$processObjects|?{$_.ProcessId -eq $processId}|%{if($_.GetOwner().User){$_.GetOwner().Domain+"\"+$_.GetOwner().User}else{'unknown'}}|select -unique
}else{
$process.UserName
}
$previousOwner
}else{
$previousOwner
}
$matchedLines = $netStat|findstr $processId
write-host "Process Id $processId matched $($matchedLines.count) lines"
foreach($matchedLine in $matchedLines){
$line = $matchedLine.Split('') | where{$_ -ne ""} # remove empty lines
$leftCount = $line[1].LastIndexOf(':')
$rightCount = $line[2].LastIndexOf(':')
$sourceEndpoint=$line[1].SubString(0,$leftCount)+':'+$line[1].SubString($leftCount+1,($line[1].Length-$leftCount-1))
$destinationEndpoint=$line[2].SubString(0,$rightCount)+':'+$line[2].SubString($rightCount+1,($line[2].Length-$rightCount-1))
$results += [PSCustomObject]@{
ComputerName = $env:computername
ProcessName = $processName
PID = $processId
Protocol = $line[0]
SourceEndPoint = $sourceEndpoint
DestinationEndpoint = $destinationEndpoint
#LocalAddress = $line[1].SubString(0,$leftCount)
#LocalPort = $line[1].SubString($leftCount+1,($line[1].Length-$leftCount-1))
#RemoteAddress = $line[2].SubString(0,$rightCount)
#RemotePort = $line[2].SubString($rightCount+1,($line[2].Length-$rightCount-1))
ConnectionStatus = $(if(!($line[3] -match '\d')){$line[3]}) # Checking if the connection contains any empty string.
processOwner=$processOwner
}
}
$previousPid=$processId
}
if($results){
return $results
}elseif($processNames -and $portNumbers){
write-host "$processNames not found on $portNumbers" -ForegroundColor Yellow
return $null
}else{
write-host "No processes matched." -ForegroundColor Yellow
return $null
}
}else{
write-host "No processes matched." -ForegroundColor Yellow
return $null
}
}
$results=@()
foreach ($computerName in $computerNames){
$session=New-PSSession -ComputerName $computerName
if($session.state -eq 'Opened'){
$result=invoke-command -ComputerName $computerName -scriptblock{
param($importFunc,$x,$y)
write-host "Executing function on $env:computername"
[scriptblock]::create($importFunc).invoke($x,$y)
} -args ${function:getProcessConnections},$processNames,$portNumbers
if($result){
$results+=$result
}else{
write-host "No matches on $computerName"
}
Remove-PSSession $session
}else{
write-warning "Unable to connect to $computername"
}
}
return $results
}
$excludeProcesses=@(
'Idle',
'SecurityHealthService',
'services',
'System',
'svchost'
)
$establishedConnections=invokeGetProcessPorts $computer|?{$_.ConnectionStatus -eq 'ESTABLISHED' -and $_.ProcessName -notin $excludeProcesses}
$checkDynamicPorts={
$tcpSettings=Get-NetTCPSetting -Setting Internet
$ephemeralStartPort=$tcpSettings.DynamicPortRangeStartPort
$ephemeralEndPort=$ephemeralStartPort+$tcpSettings.DynamicPortRangeNumberOfPorts
return [PSCustomObject][ordered]@{
computerName=$env:computername
ephemeralStartPort=$ephemeralStartPort
ephemeralEndPort=$ephemeralEndPort
}
}
$dynamicPorts=invoke-command -ComputerName $computer -ScriptBlock $checkDynamicPorts
$result=($establishedConnections|Group-Object 'ProcessName','SourceEndPoint' | `
%{$_.Group|Select 'ProcessName','Protocol','ProcessOwner','SourceEndPoint','DestinationEndPoint' -First 1} | `
?{($_.SourceEndPoint -split ':'|select -last 1) -lt $dynamicPorts.ephemeralStartPort -or ($_.DestinationEndPoint -split ':'|select -last 1) -lt $dynamicPorts.ephemeralStartPort} | `
Sort 'ProcessName','SourceEndPoint','DestinationEndPoint'|ft|out-string).trim()
return [PSCustomObject][ordered]@{
computerName=$env:computername
connections=$result
}
}
function checkUsers($computername=$env:computername){
$excludeServiceNames='conhost|dllhost|ctfmon|audiodg|cytray|ApplicationFrameHost|SYSTEM|LOCAL SERVICE|NETWORK SERVICE|DWM-\d+|UMFD-\d+'
$volumes = (gwmi -Class win32_volume -computername $computername -Filter "DriveType!=5" -ea stop| `
?{$_.DriveLetter -ne $isnull}|Select-object `
@{Name="Letter";Expression={$_.DriveLetter}}, `
@{Name="Label";Expression={$_.Label}}, `
@{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}}, `
@{Name="Utilization";Expression={"{0:N2} %" -f ((($_.Capacity-$_.FreeSpace)/$_.Capacity)*100)}} | `
sort -Property Letter|Out-String).Trim()
$topProcesses=(get-process -computername $computername|Group-Object -Property ProcessName | `
Select-Object -First 10 | `
Select Name, `
@{N='Memory (MB)';E={[math]::Round((($_.Group|Measure-Object WorkingSet -Sum).Sum/1MB),2)}} | `
Sort-Object -Property "Memory (MB)" -Descending| `
?{$_.Name -ne '' -and $_.Name -notmatch $excludeServiceNames}| `
Out-String).trim()
$memoryUsagePerUser=(get-wmiobject win32_process -computername $computername|select @{N='User';E={$_.getowner().user}}, WorkingSetSize | group user|
select Name, @{N='Memory (MB)';E={[math]::Round(($_.Group.WorkingSetSize | `
Measure-Object -Sum).Sum / 1Mb,2)}}|sort-object -property "Memory (MB)" -Descending| `
?{$_.Name -ne '' -and $_.Name -notmatch $excludeServiceNames}| `
Out-String).trim()
function getSessionsInfo([string[]]$computername=$env:computername){
$results=@()
function getDisconnectedSessionInfo($thisLine,$computer){
# convert multiple spaces into single space and split pieces into array
$sessionArray = $thisLine.Trim() -Replace '\s+',' ' -Split '\s'
$properties = @{
UserName = $sessionArray[0]
ComputerName = $computer
}
$properties.SessionName = $null
$properties.SessionId = $sessionArray[1]
$properties.State = $sessionArray[2]
$properties.IdleMinutes=.{
[string]$x=$sessionArray[3]
switch -regex ($x){
'\.' {0;break}
'\+' {$dayMinutes=.{[void]($x -match '^(\d+)\+');[int]($matches[1])*1440}
$hourMinutes=.{[void]($x -match '\+(.*)$');([TimeSpan]::Parse($matches[1])).totalMinutes}
$dayMinutes+$hourMinutes
break;
}
'\:' {try{
([TimeSpan]::Parse($x)).totalMinutes
}catch{
"Invalid value: $x"
}
break
}
default {$x}
}
}
$properties.LogonTime = $sessionArray[4..6] -join ' '
$result=New-Object -TypeName PSCustomObject -Property $properties
return $result
}
function getActiveSessionInfo($thisLine,$computer){
$sessionArray = $thisLine.Trim() -Replace '\s+',' ' -Split '\s'
$properties = @{
UserName = $sessionArray[0]
ComputerName = $computer
}
$properties.SessionName = $sessionArray[1]
$properties.SessionId = $sessionArray[2]
$properties.State = $sessionArray[3]
$properties.IdleMinutes=.{
$x=$sessionArray[4]
switch -regex ($x){
'\.' {0;break}
'\+' { $dayMinutes=.{[void]($x -match '^(\d+)\+');[int]($matches[1])*1440}
$hourMinutes=.{[void]($x -match '\+(.*)$');([TimeSpan]::Parse($matches[1])).totalMinutes}
$dayMinutes+$hourMinutes
break;
}
'\:' { $timeFragments=[regex]::match($x,'(\d+)\:(\d+)')
[Int16]$hours=$timeFragments.Groups[1].Value
[Int16]$minutes=$timeFragments.Groups[2].Value
$minutes + ($hours * 60)
break
}
default {$x}
}
}
$properties.LogonTime = $sessionArray[5..($sessionArray.GetUpperBound(0))] -join ' '
$result=New-Object -TypeName PSCustomObject -Property $properties
return $result
}
foreach ($computer in $computername){
try {
# Perusing legacy commandlets as there are no PowerShell equivalents at this time
$sessions=quser /server:$computer 2>&1 | Select-Object -Skip 1
ForEach ($line in $sessions) {
$fragments = $line.Trim() -Replace '\s+',' ' -Split '\s'
$disconnectedSession=$fragments[2] -eq 'Disc' -or $fragments[3] -eq 'Disc'
if ($disconnectedSession){
$result=getDisconnectedSessionInfo $line $computer
}else{
$result=getActiveSessionInfo $line $computer
}
if($result){$results+=$result}
}
}catch{
$results+=New-Object -TypeName PSCustomObject -Property @{
ComputerName=$computer
Error=$_.Exception.Message
} | Select-Object -Property UserName,ComputerName,SessionName,SessionId,State,IdleMinutes,LogonTime,Error|sort -Property UserName
}
}
return $results
}
$activeUsers=((getSessionsInfo $computername|?{$_.State -eq 'Active'}).UserName|out-string).trim()
return [PSCustomObject][ordered]@{
computername=$computername
memoryUsagePerUser=$memoryUsagePerUser
activeUsers=$activeUsers
volumes=$volumes
topProcesses=$topProcesses
}
}
function combineObjects($objects,$verbose=$false){
$combinedMembers=@()
foreach($object in $objects){
if($object.gettype().BaseType.Name -eq 'Object'){
$combinedMembers+=get-member -InputObject $object -MemberType NoteProperty
}else{
write-warning "This item is NOT an object:`r`n$object"
}
}
$newObject=New-Object -TypeName PSObject
foreach($property in $combinedMembers){
$propertyName=$property.Name
$value=$property.Definition -replace '^.*='
$propertyExists=$propertyName -in ($newobject|get-member).Name
if(!$propertyExists){
Add-Member -InputObject $newObject -MemberType NoteProperty -Name $propertyName -Value $value -Force
}elseif($verbose){
$previousValue=($newobject|get-member|?{$_.Name -eq $propertyName}).Definition -replace '^.*='
write-warning "$propertyName value of previous value of $previousValue has NOT been replaced with $value"
}
}
return $newObject
}
function windowsDiscovery($computer=$env:computername){
$session=try{New-PSSession $computer}catch{$false}
if($session){
Remove-PSSession $session
$bios=checkBios $computer
$spectreMitigation=checkSpectreVulnerability $computer
$network=checkNetwork $computer
$security=checkSecurity $computer
$hyperV=checkHyperVConfigs $computer
$installedApps=checkInstalledApps $computer
$iisInfo=checkIis $computer
$dotNetInfo=checkDotNet $computer
$connections=checkConnections $computer
$users=checkUsers $computer
$getIpCommand={
$regexIpv4 = '\b(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b'
#[Net.DNS]::GetHostEntry($env:computername).AddressList.IPAddressToString|?{$_ -match $regexIpv4}
$defaultInterface=Get-NetRoute -DestinationPrefix "0.0.0.0/0"
return (Get-NetIPAddress -InterfaceIndex $defaultInterface.ifIndex).IPAddress -match $regexIpv4
}
#$getAntivirus={((get-wmiobject -class "Win32_Process" -namespace "root\cimv2"|where-object {$_.Name -match "antivirus|endpoint|protection|security|defender|msmpeng"}).Name | Out-String).Trim()}
$computerObject=[pscustomobject][ordered]@{
computerName=$computer
ipv4Address=(invoke-command -computername $computer -scriptblock $getIpCommand|out-string).trim()
lastOsUpdate=Invoke-command -ComputerName $computer -ScriptBlock {(Get-HotFix | Measure-Object InstalledOn -Maximum).Maximum.ToString().Trim()}
#antivirus=Invoke-command -ComputerName $computer -ScriptBlock $getAntivirus
}
#$speculationControlSettings=checkSpeculationControls $computer
$thisSystem=combineObjects @($computerObject,$bios,$spectreMitigation,$hyperV,$network,$security,$installedApps,$iisInfo,$dotNetInfo,$connections,$users)
}else{
Write-Warning "$computer is not Reachable from $env:computername via WinRM"
$thisSystem=$false
}
if($thisSystem){
return $thisSystem|Select-Object -Property * -ExcludeProperty PSComputerName,RunspaceId,PSShowComputerName
}else{
return [PSCustomObject]@{
computerName = $computer+' Unreachable'
ipv4Addresses=[Net.DNS]::GetHostEntry($computer).AddressList.IPAddressToString|?{$_ -match $regexIpv4}
}
}
}
$thisSystem=windowsDiscovery
Categories: