PowerShell: Windows Servers Discovery

<# Servers-Discovery.ps1
Version: 0.02

Purpose: to generate a CSV spreadsheet with information about servers on the domain
1. Query Active Directory for a list of server names
2. Probe each server for its general information (IPs, Machine Type, CPU, Memory, Storage, Last Update, Antivirus, etc.)
3. Detect potential security vulnerabilities
- List local admin accounts
- Detect presence of LAPS
- Detect known exploitable protocol level vulnerabilities
- Detect common vulnerabilities

Requirements:
1. Remote Windows machines must have WinRM or WMI RPC Service enabled
2. Network access to the servers subnet from the jump box is assumed

Future development:
1. Display the host name if node is a virtual machine
a. Hyper-V: (done)
b. VMware: getVmwareHostname $guestVMName (code to be written)
2. Label machineTypes correctly: VMware (done), Hyper-V (done), AWS, Azure, Google, etc.
3. Remotely enable WinRM on machines that does not have it enabled (done, but not 100% effective)
4. Optimize the code so that all queries would be processed in one pass per node, instead of having to run multiple queries to assign various variables
5. Run in the context of a domain administrator
6. Perform a server subnets scan and resolve all servernames, then merge that list with the list obtained from AD.
7. Collect information about network devices
8. Collect information about Linux machines
9. Detect whether the computer objects are Microsoft clustered roles, rather than full Windows instances
10. Deal with Docker containers
#>

# Regex key to search for server names: look back to CN= as $0, match anything but a comma as $1, capture CN|OU as $2,separated by =, capture next part as $3
$regexServers="(?<=CN=)(.*?),(OU|CN)=([^,]+)"

# Collect server names and their containers
$servers=dsquery * -Filter "(&(objectCategory=computer)(operatingSystem=*server*))" -limit 1000000| %{[void]($_ -match $regexServers);$matches[1];$matches[3]}|select @{Name="Name";expression={$matches[1]}},@{Name="container";expression={$matches[3]}}|sort-object -Property Name -Unique

# If this error occurs: "dsquery failed:The specified domain either does not exist or could not be contacted." Then, check default DNS servers on the jump box. Sometimes, VPN connections may inject additional DNS entries that will lead to this run-time error.

# Set CSV export location
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$csvExport="$scriptPath`\serversDiscovery.csv"

# Init output as an empty array
$GLOBAL:computerObjects=@()

# Init other variables
$hklm = 2147483650
$hyperVHostKey="HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters"
$hyperVHostKeyValue="HostName"
$domain=(net config workstation) -match 'Workstation domain\s+\S+$' -replace '.+?(\S+)$','$1';
$defaultDomainAdmin="$domain\Administrator"

function getVmwareHostname($vmName){
# Install VMWare PowerCLI if it's not available in the system
if(!(Get-Command -Module VMWare*)){
Install-Module -Name VMware.PowerCLI -Scope CurrentUser;
Set-PowerCLIConfiguration -Scope AllUsers -ParticipateInCeip $false -InvalidCertificateAction Ignore
}

# Connect to our vCenter Server using the logged in credentials
$vmwareServerName="192.168.100.100"
Connect-VIServer $vmwareServerName

# Collect Hostname
#$thisVM = Get-VM -Name localhost;
#Get-VMHost -VM $thisVM
$host=(Get-VM -Name $vmName | Select @{N="Host";E={$_.Host.Name}}).Host
}

function getHyperVHostname($guestVMName){
$Hive = [Microsoft.Win32.RegistryHive]::LocalMachine;
$KeyPath = 'SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters';
$Value = 'HostName';
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $guestVMName);
$key = $reg.OpenSubKey($KeyPath);
return $key.GetValue($Value) ;
}

Function checkServiceAccount($searchAccount,$sherver){

# Original function is meant to query multiple machines. It's now adapted to just one machine
$servers=$sherver
$runas=$searchAccount
$scheduledTasksMatch="";
$servicesMatch="";
$result="";

Function Search-ScheduledTasks{
Param(
[array]$ComputerNames = $servers, #Accepts input and cast it as an array
[string]$runasUser=$runas
)
Begin{
$Results = @() #Initializes an empty array
}
Process{
If ($_){ #Checks if this function is being called via pipe command. If so, use set $ComputerNames variable as pipe
$ComputerNames = $_
}
ForEach ($Computer in $ComputerNames){
If (Test-Connection $Computer -Quiet){ #Checks for connectivity before proceeding
# Use the legacy schtasks command from localhost to query remote machine and format an output int CSV format
$tasksAsCSV = schtasks.exe /query /s $Computer /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 | Where { $_.TaskName -ne "TaskName" -and $_."Run As User" -eq $runasUser}

#Appends this result into array collection named results.
$Results += $result
}
} #end foreach
}
End{
if ($Results){
Return $Results."Task To Run"
}
else {
"No Scheduled Events were found for user $runasUser.";
}
}
} #end Search-ScheduledTasks function

$servicesMatch=(Get-Wmiobject win32_service -ComputerName $sherver | where-object{$_.startname -like $runasUser}|select @{name="ServiceName";expression={$_.name}}).ServiceName|out-string
$scheduledTasksMatch = Search-ScheduledTasks;

if($servicesMatch){$result+=("$servicesMatch").Trim()}else{"No services are currently using the $searchAccount."}
if($scheduledTasksMatch){$result+="`n$scheduledTasksMatch"}
return $result;
}

# Function obtained from http://ajitgupta.net/2017/08/26/determine-windows-activation-status-with-powershell/
# Author: AJIT GUPTA
function Get-ActivationStatus {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$DNSHostName = $Env:COMPUTERNAME
)
process {
try {
$wpa = Get-WmiObject SoftwareLicensingProduct -ComputerName $DNSHostName `
-Filter "ApplicationID = '55c92734-d682-4d71-983e-d6ec3f16059f'" `
-Property LicenseStatus -ErrorAction Stop
} catch {
$status = New-Object ComponentModel.Win32Exception ($_.Exception.ErrorCode)
$wpa = $null
}
$out = New-Object psobject -Property @{
ComputerName = $DNSHostName;
Status = [string]::Empty;
}
if ($wpa) {
:outer foreach($item in $wpa) {
switch ($item.LicenseStatus) {
0 {$out.Status = "Unlicensed"}
1 {$out.Status = "Licensed"; break outer}
2 {$out.Status = "Out-Of-Box Grace Period"; break outer}
3 {$out.Status = "Out-Of-Tolerance Grace Period"; break outer}
4 {$out.Status = "Non-Genuine Grace Period"; break outer}
5 {$out.Status = "Notification"; break outer}
6 {$out.Status = "Extended Grace"; break outer}
default {$out.Status = "Unknown value"}
}
}
} else {$out.Status = $status.Message}
return $out.Status
}
}

function getLatestWinDefenderVersion{
$regexGetVersion='(<[^>]+>|[:\s]+|Version)' #Test regex at RegexStorm.net
$regexGetReleaseDate='(<[^>]+>|^\s|\s+$|Released:)'
$winDefenderDefURL="https://www.microsoft.com/en-us/wdsi/definitions"
$count=0;
do{
$count++;
try{
$html = Invoke-WebRequest –Uri $winDefenderDefURL -Method Get -UseBasicParsing;
}
catch{

$_.Exception.Message;
$html=$False
}
}
until ($html -or ($count -eq 3))
$GLOBAL:latestVersion=($html.tostring() -split "[`r`n]" | select-string "Version:") -replace $regexGetVersion;
$GLOBAL:releaseDate=($html.tostring() -split "[`r`n]" | select-string "Released:") -replace $regexGetReleaseDate -replace " ";
#$releaseDateLocalTime=[System.TimeZoneInfo]::ConvertTimeFromUtc($releaseDate,[System.TimeZoneInfo]::FindSystemTimeZoneById((Get-WmiObject win32_timezone).StandardName))
}

function checkWinDefender($remoteSherver){
if(!($latestVersion) -or !($releaseDate)){getLatestWinDefenderVersion}
$x=$latestVersion;
$y=$releaseDate;

function localfunc($x,$y){
$latestVersion=$x;
$releaseDate=$y;
try{
$regexGetVersion='(<[^>]+>|[:\s]+|Version)' #Test regex at RegexStorm.net
$regexGetReleaseDate='(<[^>]+>|^\s|\s+$|Released:)'
$mpStatus=Get-MpComputerStatus
$actualVersion=$mpStatus.AntivirusSignatureVersion -replace $regexGetVersion
$lastUpdated=$mpStatus.AntispywareSignatureLastUpdated

$releaseDateLocalTime=[System.TimeZoneInfo]::ConvertTimeFromUtc($releaseDate,[System.TimeZoneInfo]::FindSystemTimeZoneById((Get-WmiObject win32_timezone).StandardName))
$timeDifference = New-TimeSpan -Start $lastUpdated -End $releaseDate
$daysDifference=$timeDifference.Days

"It has been $daysDifference day(s) between Last Update: $lastUpdated and New Release: $releaseDate";
if ($actualVersion -ne $latestVersion){
return "Malware Antivirus Signature version $actualVersion on this system does not match the Latest version of $latestVersion";
#Update-MPSignature;
}
else{
return "Excellent! Malware Antivirus Definition $actualVersion on this computer matches the Microsoft release.";
}
return "Please note that Microsoft Windows Defender should be disabled if there's an Enterprise Antivirus scanner installed on this system.";
}
catch{
return "Windows Defender is NOT enabled on this system.";
}
}

$result=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc,$x,$y)
[ScriptBlock]::Create($importedFunc).Invoke($x,$y)
} -ArgumentList ${function:localfunc},$x,$y

return $result;
}

function checkRDP($remoteSherver){
function localfunc{
$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
switch ($encryptionLevel){
1 {$compliant="Low";}
2 {$compliant="Client Compatible";}
3 {$compliant="High";}
4 {$compliant="FIPS Compliant";}
}
if($rdpAuth){return "RDP Network Authentication Requirement: passed!`nRDP Encryption Level: $compliant";}else{return "RDP Network Authentication Requirement: Fail";}
}

$result=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc)
[ScriptBlock]::Create($importedFunc).Invoke()
} -ArgumentList ${function:localfunc}

return $result;
}

function checkSpectreVulnerability($remoteSherver){
function localfunc{
$regexOctets="([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)"
$patchedVersion="10.0.14393.2842"
$actualVersion=(Get-Item C:\Windows\system32\mcupdate_genuineintel.dll | select VersionInfo).VersionInfo.ProductVersion

$actualVersion -match $regexOctets;
[string]$a1=$matches[1].PadLeft(3,'0');
[string]$a2=$matches[2].PadLeft(8,'0');
[string]$a3=$matches[3].PadLeft(8,'0');
[string]$a4=$matches[4].PadLeft(8,'0');
[single]$a=$a1+$a2+$a3+$a4

$patchedVersion -match $regexOctets;
[string]$p1=$matches[1].PadLeft(3,'0');
[string]$p2=$matches[2].PadLeft(8,'0');
[string]$p3=$matches[3].PadLeft(8,'0');
[string]$p4=$matches[4].PadLeft(8,'0');
[single]$p=$p1+$p2+$p3+$p4

"System version: $actualVersion VS minimum version required: $patchedVersion"

if($a -ge $p){return "Spectre meltdown vulnerabilities: Pass";}else{return "Spectre meltdown vulnerabilities: Fail";}
}

$result=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc)
[ScriptBlock]::Create($importedFunc).Invoke()
} -ArgumentList ${function:localfunc}

return $result;
}

function checkOtherVulnerabilities($remoteSherver){
function localfunc{
$result="";

# Checking IE
$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")
)
$result+="Internet Explorer: "
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
}
$result+="$ieResult";

# Checking Memory Management
$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")
)
$result+="`nMemory Management Registry Keys: "
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;
}
$result+="$memResult";

# Checking Remote Code Execution
$minVersion=14
$vcVersions=(Get-ItemProperty Registry::HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue| where {$_.displayname -like "Microsoft Visual C++*"} | Select-Object DisplayVersion)
foreach ($version in $vcVersions){
if($version.DisplayVersion -ge $minVersion){
$safeFlag=$True;
}
}
if ($safeFlag){
$result+="`nMS11-025 (MFC Remote Code Execution): Pass"
}
else{$result+="`nMS11-025 (MFC Remote Code Execution): Fail"}

# Checking unquoted service path enumeration
$unquotedServicePathItems=(wmic service get name","displayname","pathname","startmode |findstr /i "auto" |findstr /i /v "c:\windows\\" |findstr /i /v "''").Trim()
if ($unquotedServicePathItems){$result+="`nUnquoted Service Path Enumeration Vulnerabilities: Fail."}else{$result+="`nUnquoted Service Path Enumeration Vulnerabilities: Passed."}

# Checking LAPS
if(Get-ChildItem 'C:\Program Files\LAPS\CSE\Admpwd.dll' -ErrorAction SilentlyContinue){$result+="`nLAPS is installed."}else{$result+="`nLAPS is not installed.";}

# Checking SNMP
try{
$permittedManagers=Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\SNMP\Parameters\PermittedManagers" -ErrorAction SilentlyContinue;
$validCommunities=Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\SNMP\Parameters\ValidCommunities" -ErrorAction SilentlyContinue;
}
finally{
if (($permittedManagers) -and ($validCommunities)){
$result+="`nSNMP: permittedManagers and validCommunities values are detected.";
}else{
$result+="`nSNMP: permittedManagers and validCommunities values are NOT detected.";
}
}

# Checking IISCrypto
$iisServer=Get-Service -Name 'IISADMIN' -ErrorAction SilentlyContinue | Select -ExpandProperty Status
if($iisServer){
$result+="`nIIS is detected on this system with status $iisServer"
$regHiveSSL30="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server"
$regHiveTLS10="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server"
$regHiveTLS11="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server"
$regHiveTLS12="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
$isSSL30Enabled = Get-ItemProperty -Path $regHiveSSL30 -Name "Enabled" -ErrorAction SilentlyContinue
$isTLS10Enabled = Get-ItemProperty -Path $regHiveTLS10 -Name "Enabled" -ErrorAction SilentlyContinue
$isTLS11Enabled = Get-ItemProperty -Path $regHiveTLS11 -Name "Enabled" -ErrorAction SilentlyContinue
$isTLS12Enabled = Get-ItemProperty -Path $regHiveTLS12 -Name "Enabled" -ErrorAction SilentlyContinue
If ($isSSL30Enabled) {$result+="`nSSL 3.0 is Enabled.";} ElseIf ($isSSL30Enabled -eq 0){$result+="`nSSL 3.0 is Disabled.";} else {$result+="`nSSL 3.0 SCHANNEL is not detected.";}
If ($isTLS10Enabled) {$result+="`nTLS 1.0 is Enabled.";} ElseIf ($isTLS10Enabled -eq 0){$result+="`nTLS 1.0 is Disabled.";} else {$result+="`nTLS 1.0 SCHANNEL is not detected.";}
If ($isTLS11Enabled) {$result+="`nTLS 1.1 is Enabled.";} ElseIf ($isTLS11Enabled -eq 0){$result+="`nTLS 1.1 is Disabled.";} else {$result+="`nTLS 1.2 SCHANNEL is not detected.";}
If ($isTLS12Enabled) {$result+="`nTLS 1.2 is Enabled.";} ElseIf ($isTLS12Enabled -eq 0){$result+="`nTLS 1.2 is Disabled.";} else {$result+="`nTLS 1.2 SCHANNEL is not detected.";}
#If([System.IO.File]::Exists("C:\Windows\IISCryptoCli.exe")){$result+="`nIISCryptoCli is available."}else{$result+="`nIISCryptoCli is not available at C:\Windows.";}
if((Get-Command IISCryptoCli.exe -ErrorAction SilentlyContinue) -or (Get-Command IISCrypto.exe -ErrorAction SilentlyContinue)){$result+="`nIIS Crypto is available on this system."}else{$result+="`nIIS Crypto is not available on this system.";}
}

return $result;
}

$otherVulnerabilities=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc)
[ScriptBlock]::Create($importedFunc).Invoke()
} -ArgumentList ${function:localfunc}

return $otherVulnerabilities;
}

function getlocalAdministrators($remoteSherver){
invoke-command -computername $remoteSherver {net localgroup administrators | where {$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4}
}

function checkServer($name){
$computerObject=New-Object PSObject
$ips="Unknown";
$machineType="Unknown";
$machineModel="Unknown";
$os="Unknown";
$cpu=$cpuLoad="Unknown";
$memory="Unknown";
$volumes="Unknown";
$lastUpdate="Unknown";
$antivirusName="Unknown";
$activationStatus="Unknown";
$serial="Unknown";
$cpuObject=@();
$servicesWithDefaultDomainAdminUsage="";
$winDefenderStatus="";
$rdpSecurity="";
$spectreVulnerability="";
$otherVulnerabilities="";
$localAdministrators="";
try{
$ips=([System.Net.Dns]::GetHostAddresses($name)|?{$_.AddressFamily -eq "InterNetwork"}|%{$_.IPAddressToString;}|out-string).Trim();
$machineModel=(Get-WmiObject -Class Win32_ComputerSystem -ComputerName $name -ea stop).Model
$cpuObject = Get-WmiObject -computername $name win32_processor -ea stop |select Name,NumberOfCores,LoadPercentage
$cpu=($cpuObject|select Name,NumberOfCores|Out-String).Trim()
$cpuLoad = ($cpuObject| Measure-Object -property LoadPercentage -Average | Select @{Name="CurrentLoad";Expression={"{0:N2} %" -f ($_.Average)}}).CurrentLoad
$osAndMemory = gwmi -Class win32_operatingsystem -computername $name -ea stop| select @{Name="os";Expression={$_.Caption}},@{Name="Memory";Expression={"{0:N2} GB" -f ($_.TotalVisibleMemorySize / 1048576)}},@{Name = "Utilization"; Expression = {"{0:N2} %" -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize) }}
if($osAndMemory){
$os=$osAndMemory.os;
$memory=$osAndMemory.Memory;
$memoryUtilization=$osAndMemory.Utilization;
}
$volumes = (gwmi -Class win32_volume -computername $name -Filter "DriveType!=5" -ea stop| ?{$_.DriveLetter -ne $isnull}|Select-object @{Name="Letter";Expression={$_.DriveLetter}},@{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}},@{Name = "Utilization"; Expression = {"{0:N2} %" -f ((($_.Capacity-$_.FreeSpace) / $_.Capacity)*100) } } | Out-String).Trim()
$activationStatus=Get-ActivationStatus $name
$serial=(Get-WMIObject -Class Win32_BIOS -ComputerName $name).SerialNumber
#[string]$lastUpdate=gwmi win32_quickfixengineering -ComputerName $name |select HotFixID,InstalledOn|Select-Object -first 1
$lastUpdate=(Get-HotFix -ComputerName $name | Measure-Object InstalledOn -Maximum).Maximum.ToString().Trim()
$antivirusName=((get-wmiobject -class "Win32_Process" -namespace "root\cimv2" -ComputerName $name | where-object {$_.Name.ToLower() -match "antivirus|endpoint|protection|security|defender|msmpeng"}).Name | Out-String).Trim()
$servicesWithDefaultDomainAdminUsage=(checkServiceAccount $defaultDomainAdmin $name|Out-String).Trim()
if ($winRmAvailable){
$localAdministrators=(getlocalAdministrators $name|Out-String).Trim()
$winDefenderStatus=(checkWinDefender $name|Out-String).Trim()
$rdpSecurity=checkRDP $name
$spectreVulnerability=(checkSpectreVulnerability $name|Out-String).Trim()
$otherVulnerabilities=(checkOtherVulnerabilities $name|Out-String).Trim();
}else{
$localAdministrators=$otherVulnerabilities=$winDefenderStatus=$rdpSecurity=$spectreVulnerability="Unable to check due to WinRM connection errors."
}
}
catch{
if(!($machineModel)){$machineModel="Unknown"};
if(!($machineType)){$machineType="Unknown"};
if(!($osAndMemory)){$os=$memory=$memoryUtilization="Unknown";}
if(!($ips)){$ips="Unknown";}
if(!($cpuObject)){$cpu=$cpuLoad="Unknown";}
if(!($volumes)){$volumes="Unknown";}
if(!($serial)){$serial="Unknown"}
Continue;
}
finally{
switch -wildcard ($machineModel){
"VMware*" {$machineType="VMWare Virtual Machine";$hostname="N/A";}
"Virtual Machine" {$machineType="Hyper-V Virtual Machine";$hostname=getHyperVHostname $name}
"*HVM*" {$machineType="AWS Virtual Machine";$hostname="N/A";}
"*.*" {$machineType="AWS Virtual Machine";$hostname="N/A";}
"Unknown" {$machineType="Unknown";$hostname="Unknown";}
default {if (validateAzureVM $name){$machineType="Azure Virtual Machine"}else{$machineType="Physical Machine";$hostname="N/A"};}
}
$computerObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $name
$computerObject | Add-Member -MemberType NoteProperty -Name "ips" -Value $ips
$computerObject | Add-Member -MemberType NoteProperty -Name "machineType" -Value $machineType
$computerObject | Add-Member -MemberType NoteProperty -Name "hostname" $hostname
$computerObject | Add-Member -MemberType NoteProperty -Name "machineModel" -Value $machineModel
$computerObject | Add-Member -MemberType NoteProperty -Name "os" -Value $os
$computerObject | Add-Member -MemberType NoteProperty -Name "activationStatus" -Value $activationStatus
$computerObject | Add-Member -MemberType NoteProperty -Name "serial" -Value $serial
$computerObject | Add-Member -MemberType NoteProperty -Name "cpu" -Value $cpu
$computerObject | Add-Member -MemberType NoteProperty -Name "cpuUtilization" -Value $cpuLoad
$computerObject | Add-Member -MemberType NoteProperty -Name "memory" -Value $memory
$computerObject | Add-Member -MemberType NoteProperty -Name "memoryUtilization" -Value $memoryUtilization
$computerObject | Add-Member -MemberType NoteProperty -Name "volumes" -Value $volumes
$computerObject | Add-Member -MemberType NoteProperty -Name "container" -Value $container
$computerObject | Add-Member -MemberType NoteProperty -Name "lastUpdate" -Value $lastUpdate
$computerObject | Add-Member -MemberType NoteProperty -Name "antivirus" -Value $antivirusName
$computerObject | Add-Member -MemberType NoteProperty -Name "localAdministrators" -Value $localAdministrators
$computerObject | Add-Member -MemberType NoteProperty -Name "servicesWithDefaultDomainAdminUsage" -Value $servicesWithDefaultDomainAdminUsage
$computerObject | Add-Member -MemberType NoteProperty -Name "winDefenderStatus" -Value $winDefenderStatus
$computerObject | Add-Member -MemberType NoteProperty -Name "rdpSecurity" -Value $rdpSecurity
$computerObject | Add-Member -MemberType NoteProperty -Name "spectreVulnerability" -Value $spectreVulnerability
$computerObject | Add-Member -MemberType NoteProperty -Name "otherVulnerabilities" -Value $otherVulnerabilities
}
$computerObject;
$GLOBAL:computerObjects+=,$computerObject
}

# Function obtained from https://gallery.technet.microsoft.com/scriptcenter/Detect-Windows-Azure-aed06d51
# snippet author: Craig Landis
function validateAzureVM($instance){

Function Confirm-AzureVM {

$source = @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Net.NetworkInformation;

namespace Microsoft.WindowsAzure.Internal
{
/// <summary>
/// A simple DHCP client.
/// </summary>
public class DhcpClient : IDisposable
{
public DhcpClient()
{
uint version;
int err = NativeMethods.DhcpCApiInitialize(out version);
if (err != 0)
throw new Win32Exception(err);
}

public void Dispose()
{
NativeMethods.DhcpCApiCleanup();
}

/// <summary>
/// Gets the available interfaces that are enabled for DHCP.
/// </summary>
/// <remarks>
/// The operational status of the interface is not assessed.
/// </remarks>
/// <returns></returns>
public static IEnumerable<NetworkInterface> GetDhcpInterfaces()
{
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue;
if (!nic.Supports(NetworkInterfaceComponent.IPv4)) continue;
IPInterfaceProperties props = nic.GetIPProperties();
if (props == null) continue;
IPv4InterfaceProperties v4props = props.GetIPv4Properties();
if (v4props == null) continue;
if (!v4props.IsDhcpEnabled) continue;

yield return nic;
}
}

/// <summary>
/// Requests DHCP parameter data.
/// </summary>
/// <remarks>
/// Windows serves the data from a cache when possible.
/// With persistent requests, the option is obtained during boot-time DHCP negotiation.
/// </remarks>
/// <param name="optionId">the option to obtain.</param>
/// <param name="isVendorSpecific">indicates whether the option is vendor-specific.</param>
/// <param name="persistent">indicates whether the request should be persistent.</param>
/// <returns></returns>
public byte[] DhcpRequestParams(string adapterName, uint optionId)
{
uint bufferSize = 1024;
Retry:
IntPtr buffer = Marshal.AllocHGlobal((int)bufferSize);
try
{
NativeMethods.DHCPCAPI_PARAMS_ARRAY sendParams = new NativeMethods.DHCPCAPI_PARAMS_ARRAY();
sendParams.nParams = 0;
sendParams.Params = IntPtr.Zero;

NativeMethods.DHCPCAPI_PARAMS recv = new NativeMethods.DHCPCAPI_PARAMS();
recv.Flags = 0x0;
recv.OptionId = optionId;
recv.IsVendor = false;
recv.Data = IntPtr.Zero;
recv.nBytesData = 0;

IntPtr recdParamsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(recv));
try
{
Marshal.StructureToPtr(recv, recdParamsPtr, false);

NativeMethods.DHCPCAPI_PARAMS_ARRAY recdParams = new NativeMethods.DHCPCAPI_PARAMS_ARRAY();
recdParams.nParams = 1;
recdParams.Params = recdParamsPtr;

NativeMethods.DhcpRequestFlags flags = NativeMethods.DhcpRequestFlags.DHCPCAPI_REQUEST_SYNCHRONOUS;

int err = NativeMethods.DhcpRequestParams(
flags,
IntPtr.Zero,
adapterName,
IntPtr.Zero,
sendParams,
recdParams,
buffer,
ref bufferSize,
null);

if (err == NativeMethods.ERROR_MORE_DATA)
{
bufferSize *= 2;
goto Retry;
}

if (err != 0)
throw new Win32Exception(err);

recv = (NativeMethods.DHCPCAPI_PARAMS)
Marshal.PtrToStructure(recdParamsPtr, typeof(NativeMethods.DHCPCAPI_PARAMS));

if (recv.Data == IntPtr.Zero)
return null;

byte[] data = new byte[recv.nBytesData];
Marshal.Copy(recv.Data, data, 0, (int)recv.nBytesData);
return data;
}
finally
{
Marshal.FreeHGlobal(recdParamsPtr);
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

///// <summary>
///// Unregisters a persistent request.
///// </summary>
//public void DhcpUndoRequestParams()
//{
// int err = NativeMethods.DhcpUndoRequestParams(0, IntPtr.Zero, null, this.ApplicationID);
// if (err != 0)
// throw new Win32Exception(err);
//}

#region Native Methods
}

internal static partial class NativeMethods
{
public const uint ERROR_MORE_DATA = 124;

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpRequestParams", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpRequestParams(
DhcpRequestFlags Flags,
IntPtr Reserved,
string AdapterName,
IntPtr ClassId,
DHCPCAPI_PARAMS_ARRAY SendParams,
DHCPCAPI_PARAMS_ARRAY RecdParams,
IntPtr Buffer,
ref UInt32 pSize,
string RequestIdStr
);

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpUndoRequestParams", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpUndoRequestParams(
uint Flags,
IntPtr Reserved,
string AdapterName,
string RequestIdStr);

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpCApiInitialize", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpCApiInitialize(out uint Version);

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpCApiCleanup", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpCApiCleanup();

[Flags]
public enum DhcpRequestFlags : uint
{
DHCPCAPI_REQUEST_PERSISTENT = 0x01,
DHCPCAPI_REQUEST_SYNCHRONOUS = 0x02,
DHCPCAPI_REQUEST_ASYNCHRONOUS = 0x04,
DHCPCAPI_REQUEST_CANCEL = 0x08,
DHCPCAPI_REQUEST_MASK = 0x0F
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCPCAPI_PARAMS_ARRAY
{
public UInt32 nParams;
public IntPtr Params;
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCPCAPI_PARAMS
{
public UInt32 Flags;
public UInt32 OptionId;
[MarshalAs(UnmanagedType.Bool)]
public bool IsVendor;
public IntPtr Data;
public UInt32 nBytesData;
}
#endregion
}
}
"@

Add-Type -TypeDefinition $source

$detected = $False

[void][System.Reflection.Assembly]::LoadWithPartialName('System.Serviceprocess')

$vmbus = [System.ServiceProcess.ServiceController]::GetDevices() | where {$_.Name -eq 'vmbus'}

If($vmbus.Status -eq 'Running')
{
$client = New-Object Microsoft.WindowsAzure.Internal.DhcpClient
try {
[Microsoft.WindowsAzure.Internal.DhcpClient]::GetDhcpInterfaces() | % {
$val = $client.DhcpRequestParams($_.Id, 245)
if($val -And $val.Length -eq 4) {
$detected = $True
}
}
} finally {
$client.Dispose()
}
}
Write-Output $detected
}

$result=Invoke-Command -ComputerName $instance -ScriptBlock {
param( $importedFunc)

# Import the function from the variable inside parameters
[ScriptBlock]::Create($importedFunc).Invoke()

} -ArgumentList ${function:Confirm-AzureVM}

return $result;
}

function enableWinRm($remoteSherver){
# Enable WinRM Remotely
psexec.exe \\$remoteSherver -s C:\Windows\system32\winrm.cmd qc -quiet;

# Test to see if WinRM is indeed installed
$success=test-netconnection $remoteSherver -CommonTCPPort WINRM -InformationLevel Quiet;

return $success;
}

# function obtained from https://copdips.com/2019/09/fast-tcp-port-check-in-powershell.html
# Author: Xiang ZHU
function Test-Port {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, HelpMessage = 'Could be suffixed by :Port')]
[String[]]$ComputerName,

[Parameter(HelpMessage = 'Will be ignored if the port is given in the param ComputerName')]
[Int]$Port = 5985,

[Parameter(HelpMessage = 'Timeout in millisecond. Increase the value if you want to test Internet resources.')]
[Int]$Timeout = 1000
)

begin {
$result = [System.Collections.ArrayList]::new()
}

process {
foreach ($originalComputerName in $ComputerName) {
$remoteInfo = $originalComputerName.Split(":")
if ($remoteInfo.count -eq 1) {
# In case $ComputerName in the form of 'host'
$remoteHostname = $originalComputerName
$remotePort = $Port
} elseif ($remoteInfo.count -eq 2) {
# In case $ComputerName in the form of 'host:port',
# we often get host and port to check in this form.
$remoteHostname = $remoteInfo[0]
$remotePort = $remoteInfo[1]
} else {
$msg = "Got unknown format for the parameter ComputerName: " `
+ "[$originalComputerName]. " `
+ "The allowed formats is [hostname] or [hostname:port]."
Write-Error $msg
return
}

$tcpClient = New-Object System.Net.Sockets.TcpClient
$portOpened = $tcpClient.ConnectAsync($remoteHostname, $remotePort).Wait($Timeout)

$null = $result.Add([PSCustomObject]@{
RemoteHostname = $remoteHostname
RemotePort = $remotePort
PortOpened = $portOpened
TimeoutInMillisecond = $Timeout
SourceHostname = $env:COMPUTERNAME
OriginalComputerName = $originalComputerName
})
}
}

end {
return $result
}
}

function systemsDiscovery{

foreach($server in $servers){
$serverName=$server.Name
"Scanning $serverName ..."
$GLOBAL:container=$server.container
$GLOBAL:winRmAvailable=Test-Port -ComputerName $serverName -ErrorAction SilentlyContinue
if(!($winRmAvailable)){$GLOBAL:winRmAvailable=enableWinRm $serverName}
checkServer $serverName
}
$computerObjects|Export-Csv -Path $csvExport -NoTypeInformation
"Results have now been saved at: $csvExport"
}

systemsDiscovery;

Leave a Reply

Your email address will not be published. Required fields are marked *