# hyperVServersReport.ps1
# Version: 0.0.1
# Description:
# This script will scan for Hyper-V Hosts in the default domain,
# and create a report in HTML format of general server settings:
# - BIOS
# - OS
# - Security
# - Networking
$reportName="Hyper-V Hosts Report for Domain $env:USERDNSDOMAIN"
$reportFilePath='C:\hyperVServersReport.html'
function getAllHyperVHosts{
function getHyperVHostsInForest{
function includeRSAT{
$ErrorActionPreference='stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
$rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
$rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
$rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
$rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
$rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
# This command does not work on Windows 2012R2
#$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
#Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
#NT\CurrentVersion.
#At line:1 char:2
#+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
# + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
$releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
$osVersion=[System.Environment]::OSVersion.Version
[double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)"
$osName=(Get-WmiObject Win32_OperatingSystem).Name
#$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
# 1 {'client'}
# 2 {'domaincontroller'}
# 3 {'memberserver'}
# }
$windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
switch ($releaseId){
1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
}
switch ($osVersionMajorMinor){
{$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
{$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
{$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
{$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
}
if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
try{
# If OS is Windows Server, then install RSAT using a different method
if ($osName -match "^Microsoft Windows Server") {
# This sequence has confirmed to be valid on Windows Server 2008 R2 and above
Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
Import-Module ServerManager
Add-WindowsFeature RSAT-AD-PowerShell
}
else{
Write-Verbose "This sequence targets Windows Client versions"
$destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
Write-Host "Downloading RSAT from $link..."
Start-BitsTransfer -Source $link -Destination $destinationFile
$fileCheck=Get-AuthenticodeSignature $destinationFile
if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
$wusaCommand = $destinationFile + " /quiet"
Write-host "Installing RSAT - please wait..."
Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
}
return $true
}
catch{
write-warning "$($error[0].Exception)"
return $false
}
}else{
Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
return $true
}
}
function listAllHyperVNodes($verbose=$true){
try{
$timer=[System.Diagnostics.Stopwatch]::StartNew()
$domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}
foreach ($domain in $domains){
#[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
$allClusters=(get-cluster -domain $domain).Name
# $allClusters=(get-cluster -domain $env:USERDNSDOMAIN).Name
if($verbose){
$elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
write-host "Minutes elapsed $elapsed`: cluster names collected"
}
$allHyperVNodes=@()
foreach ($cluster in $allClusters){
$nodes=.{$x=Get-ClusterNode -Cluster $cluster -ea SilentlyContinue
if($x){
$x|Where-Object{$_.State -eq 'Up'}|Select-Object Name,@{name='Cluster';e={$cluster}}
}else{
$false
}
}
if($nodes){$allHyperVNodes+=$nodes}
}
if($verbose){
$elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
write-host "Minutes elapsed $elapsed`: Hyper Node names collected..."
}
}
return $allHyperVNodes
}catch{
Write-Error $_
return $false
}
}
try{
$null=includeRSAT;
$hyperVHosts=listAllHyperVNodes
$hyperVHostNames=$hyperVHosts|sort -property Cluster
return $hyperVHostNames
}catch{
Write-Error $_
return $false
}
}
function sortArrayStringAsNumbers([string[]]$names){
$hashTable=@{}
foreach ($name in $names){
#[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
#$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
$x=.{[void]($name -match '(?:.(\d+)+)$');($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft(8,'0')}
$hashTable.Add($name,$x)
}
$sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
return $sorted
}
write-host "Obtaining cluster names and associated hosts..."
$hyperVHostsInForest=getHyperVHostsInForest
$hyperVHosts=sortArrayStringAsNumbers $hyperVHostsInForest.Name
return $hyperVHosts
}
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 = $mitigationStatus
}
}
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){
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{
#$vmSwitch=get-vmswitch -Computername $computer
$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
$virtualMachineMigrationPerformanceOption=(Get-VMHost).VirtualMachineMigrationPerformanceOption
$cpuCompatiblityformigrationDisabled=(Get-VMProcessor -VMName *|?{$_.CompatibilityForMigrationEnabled -eq $false}).VMName
$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
virtualMachineMigrationPerformanceOption=$virtualMachineMigrationPerformanceOption
volumes=($volumes|Sort-Object|ft -HideTableHeaders|out-string).trim() -replace '\s{2}',''
cpuCompatiblityformigrationDisabled=($cpuCompatiblityformigrationDisabled|out-string).trim()
}
}
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 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 checkSystem($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
$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)
}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}
}
}
}
function getHyperVServersReport{
param(
$hyperVHosts,
$reportName="Hyper-V Hosts Report for Domain $env:USERDNSDOMAIN",
$reportFilePath='C:\hyperVServersReport.html'
)
$timer=[System.Diagnostics.Stopwatch]::StartNew()
if(!$hyperVHosts){
$hyperVHosts=getAllHyperVHosts
}
if($hyperVHosts){
$results=@()
$hostCount=$hyperVHosts.Count
for ($i=0;$i -lt $hostCount;$i++){
write-host "Scanning $($i+1) of $hostCount`: $($hyperVHosts[$i])..."
$results+=checkSystem $hyperVHosts[$i]
$elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
write-host "Minutes elapsed: $elapsed"
}
# Source code adapted from https://learn.microsoft.com/en-us/archive/blogs/brandev/powershell-quick-html-reports-using-css-stylesheet
$cssTest = "
<style>
h1, h3, h5, th { text-align: center; font-family: Segoe UI; }
table {margin: auto; font-family: Segoe UI; box-shadow: 10px 10px 5px #888; border: thin ridge grey;
#table-layout: fixed;
}
th { background: #0046c3; color: #fff; padding: 5px 10px; }
td { font-size: 11px; padding: 5px 20px; color: #000;
width: 1px;
#overflow: hidden;
white-space: pre;
}
tr { background: #b8d1f3;display: }
tr:nth-child(even) { background: #dae5f4;}
tr:nth-child(odd) { background: #b8d1f3; }
</style>
"
$css="
<style>
h1, h3, h5, th { text-align: center; font-family: Segoe UI; }
table {
border-collapse: separate;
border-spacing: 0;
border-top: 1px solid grey;
margin: auto;
box-shadow: 10px 10px 5px #888;
}
td, th {
margin: 0;
border: 1px solid grey;
white-space: pre;
border-top-width: 0px;
}
div {
width: 500px;
overflow-x: scroll;
margin-left: 5em;
overflow-y: visible;
padding: 0;
}
.headcol {
position: absolute;
width: 5em;
left: 0;
top: auto;
border-top-width: 1px;
/*only relevant for first row*/
margin-top: -1px;
/*compensate for top border*/
}
.headcol:before {
content: 'Row ';
}
.long {
background: yellow;
letter-spacing: 1em;
}
th { background: #0046c3; color: #fff; padding: 5px 10px; }
tr:nth-child(even) { background: white;}
tr:nth-child(odd) { background: #dae5f4; }
</style>
"
$results|convertTo-Html -Head $css -Body "<h1>$reportName</h1>`n<h5>Generated on $(Get-Date)</h5>"|Out-File $reportFilePath
$elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
write-host "Total run time: $elapsed"
write-host "Report path: $reportFilePath"
}else{
write-warning "Hyper-V host names are required to run this program."
}
}
getHyperVServersReport -reportName $reportName -reportFilePath $reportFilePath
Categories: