<# Systems-Inventory.ps1
Version: 0.04

Purpose: to generate a report 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 and 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 (done)
    c. get-cpucompatibility
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 (optimization is for future revisions)
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
10. Deal with Docker containers
#>

<#
################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
 
# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
 
# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
   {
   # We are running "as Administrator" - so change the title and background color to indicate this
   $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
   $Host.UI.RawUI.BackgroundColor = "Black"
   clear-host
   }
else
   {
   # We are not running "as Administrator" - so relaunch as administrator
   
   # Create a new process object that starts PowerShell
   $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
   
   # Specify the current script path and name as a parameter
   $newProcess.Arguments = $myInvocation.MyCommand.Definition;
   
   # Indicate that the process should be elevated
   $newProcess.Verb = "runas";
   
   # Start the new process
   [System.Diagnostics.Process]::Start($newProcess);
   
   # Exit from the current, unelevated, process
   exit
   }
 
Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################
#>

# User defined variables
$threshold=0.8;
$mustHaveDriveLetters='BC' 
$expectedPageDrive='B' # We're currently standardizing B drive as pagefile storage
$expectedAntivirusNames = "antivirus|endpoint|protection|defender|msmpeng|guard"
$numberOfMachinesToScan=9999

# Set CSV export location
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$csvOutput="$scriptPath`\serversInventory.csv"
$htmlOutput="$scriptPath`\serversInventory.html"

# Include prerequisites to run dsquery
if(!(get-command dsquery -InformationAction SilentlyContinue)){
	$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
	$newProcess.Arguments="Get-WindowsCapability -Online |? Name -like 'Rsat.ActiveDirectory.DS-LDS.Tools*'|Add-WindowsCapability -Online; pause"
	$newProcess.Verb = "runas";
	[System.Diagnostics.Process]::Start($newProcess);
	sleep 20;
	}

# Import the Active Directory module for the Get-ADComputer CmdLet 
if(!(get-command get-aduser)){
    Import-Module ServerManager
    Add-WindowsFeature RSAT-AD-PowerShell
    }
Import-Module ActiveDirectory 

# 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
$regexComputerNames="(?<=CN=)(.*?),(OU|CN)=([^,]+)"
# Collect server names and their containers
$servers=dsquery * -Filter "(&(objectCategory=computer)(operatingSystem=*server*))" -limit 1000000| %{[void]($_ -match $regexComputerNames);$matches[1];$matches[3]}|select @{Name="Name";expression={$matches[1]}},@{Name="container";expression={$matches[3]}}|sort-object -Property Name -Unique|Select-Object -First $numberOfMachinesToScan

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

# 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 retryScriptBlock {
            [CmdletBinding()]
            Param(
                [Parameter(Position=0, Mandatory=$true)]
                [scriptblock]$ScriptBlock,

                [Parameter(Position=1, Mandatory=$false)]
                [int]$Maximum = 5
            )

            Begin {
                $cnt = 0
            }

            Process {
                do {
                    $cnt++
                    try {
                        $ScriptBlock.Invoke()
                        return
                    } catch {
                        Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
                    }
                } while ($cnt -lt $Maximum)

                # Throw an error after $Maximum unsuccessful invocations. Doesn't need
                # a condition, since the function returns upon successful invocation.
                throw 'Execution failed.'
            }
        }

function killPidLockedFile($filename){
    function findPidOfFile{
	        param($handles,$filename)
            $lockingPids=@();
            $lastKnownPid="";
	        foreach ($line in $handles) {
                $lastKnownPid=.{
                    [void]($line -match "pid:\s(.*)\s");
                    if ($matches[1]){return $matches[1]}
                    }
		        if ($line -like "*$filename*") {
			        return $lastKnownPid;
                    }
	             }
            }

    $file=$(New-Object System.IO.FileInfo $filename)
    try{
        $fileHandle = $file.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
        $fileHandle.Close() # if file handle is open, it means that file is not locked
        }catch{
            $handles = handle.exe;
            if (!($handles)){
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))};
                choco install sysinternals -y;
                $handles = handle;
                }
            $lockingPids=findPidLockedFile $handles $filename;
            if ($lockingPids){
                write-host "Now killing procees ID: $lockingPids";
                $lockingPids|%{taskkill /F /PID $_}
                }else{
                    write-host "$filename is currently as NOT being locked";
                    }
        }
}

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 obtained from 
# 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
    }
}

# Obtain latest WinDefender definition version
function getLatestWinDefenderVersion{
    $regexGetVersion='(<[^>]+>|[:\s]+|Version)'
    $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).ToString() -split "[`r`n]";
            }
        catch{
            $_.Exception.Message;
            $html=$False            
            }
        }
        until ($html -or ($count -eq 3))
    $GLOBAL:latestVersion=($html | select-string "Version:") -replace $regexGetVersion|select -First 1;
    [datetime]$GLOBAL:releaseDate=($html | select-string "Released:") -replace $regexGetReleaseDate -replace "  ";
    #$releaseDateLocalTime=[System.TimeZoneInfo]::ConvertTimeFromUtc($releaseDate,[System.TimeZoneInfo]::FindSystemTimeZoneById((Get-WmiObject win32_timezone).StandardName))
}

function checkRDP([string]$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([string]$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
        $pass=$false

        $temp=$actualVersion -match $regexOctets;
        [int]$a1=$matches[1];
        [int]$a2=$matches[2];
        [int]$a3=$matches[3];
        [int]$a4=$matches[4];        

        $temp=$patchedVersion -match $regexOctets;
        [int]$p1=$matches[1];
        [int]$p2=$matches[2];
        [int]$p3=$matches[3];
        [int]$p4=$matches[4];

        if($a1 -ge $p1){
            if($a2 -ge $p2){
                if($a3 -ge $p3){
                    if($a4 -ge $p4){$pass=$true}
                    }
                }
            }
 
        write-host "System version: $actualVersion VS minimum version required: $patchedVersion"
        return $pass;    
    }

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

    return $result;
}

function getDiskIops{

        param(
            [string]$computername='localhost',
            [string]$driveLetter="c:"
            )
                
        if($driveLetter.Length -eq 1){$driveLetter+=":";}
        write-host "Obtaining disk speed of $driveLetter on $computername"        

        function getIops($driveLetter){
            # Set variables
            $tempDirectory="$driveLetter`\getDiskSpeed"
            New-Item -ItemType Directory -Force -Path $tempDirectory|Out-Null
            $testFile="$tempDirectory`\testfile.dat"
            $processors=(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
                               
            # Ensure that diskspd.exe is available in the system
            $diskSpeedUtilityAvailable=get-command diskspd.exe -ea SilentlyContinue
            <# Commenting out the choco install aspects to limit script runtime to only queries, instead of installing MS Utils
            if (!($diskSpeedUtilityAvailable)){
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))|Out-null
                    }    
                choco install diskspd -y |Out-null;
                refreshenv|Out-null;
            }
            #>  
            if(get-command diskspd.exe -ea SilentlyContinue){
                            try{
                $expression="Diskspd.exe -b$clusterSize -d1 -h -L -o$processors -t1 -r -w30 -c1G $testfile  2>&1";
                $testResult=invoke-expression $expression;
                }catch{                    
                            $errorMessage = $_.Exception.Message
                            $failedItem = $_.Exception.ItemName
                            Write-Host "$errorMessage $failedItem";
                            continue;
                            }
        
                $x=$testResult|select-string -Pattern "total*" -CaseSensitive|select-object -First 1|out-String
                $iops=$x.split("|")[-3].Trim()

                # Cleanup
                if (test-path $tempDirectory){
                $file=$(New-Object System.IO.FileInfo $testFile)
                if (Test-Path $testFile){
                    try {
                        $fileHandle = $file.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
                        if ($fileHandle){
                            $fileHandle.Close() # if file handle is open, it means that file is not locked
                            }
                        Remove-Item -Recurse -Force $tempDirectory
                        }
                    catch{                        
                        # file is locked
                        sleep 1
                        Remove-Item -Recurse -Force $tempDirectory
                        }
                    }
                
                }
            }else{
                $iops="diskspd required"
                }
            #$mebibytesPerSecond=$x.split("|")[-4].Trim() 
            #$mebibytesPerSecond=[math]::round($(([int]$selectedIops)/128),2)           
            return "$iops IOPS"
        }

        if ($computername -eq 'localhost'){
            $iops=getIops -driveLetter $driveLetter
            }else{
            $iops=invoke-command -ComputerName $computername -ScriptBlock{param($importedFunction,$x)
                                                                    return [ScriptBlock]::Create($importedFunction).invoke($x);        
                                                                    } -ArgumentList ${function:getIops},$driveLetter
            }
        return $iops
    }

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 checkServiceAccount{
        param(
            [string]$computername=$env:COMPUTERNAME,
            [string]$searchForAccount=$defaultDomainAdmin
        )     
        
        $servicesMatch="";
        $tasksFound = "";
        $results="";
        $matches="";
        $scheduledTaskMatches="";
                   
        # Use the legacy schtasks command from localhost to query local machine and format an output int CSV format              
        $tasks = invoke-command -ComputerName $computername -ScriptBlock{return schtasks.exe /query /V /FO CSV}

        # Process the CSV result into PowerShell. Filter entries that are not labeled as "TaskName" and by "Run as User" field matching the target account
        #$tasksFound = $tasksAsCSV | ConvertFrom-Csv | Where { $_.TaskName -ne "TaskName" -and $_."Run As User" -eq $searchForAccount}
        $tasksFound = $tasks  | ConvertFrom-Csv |? { $_.TaskName -ne "TaskName" -and $_."Run As User"  -match $searchForAccount}
        if ($tasksFound){
            $results+="Scheduled Tasks 'Run-As' account $searchForAccount found:`r`n---------------------------------------------------";
            [string]$scheduledTaskMatches=foreach ($task in $tasksFound){"---TaskName: $($task.TaskName)`r`n---Executable: $($task.'Task To Run')`r`n---Next Run: $($task.'Next Run Time')`r`n"}	
		    $results+=$scheduledTaskMatches
            }	
        
        #$servicesMatch=(Get-Wmiobject win32_service | where-object{$_.startname -like $searchForAccount}|select @{name="ServiceName";expression={$_.name}}).ServiceName|out-string
	    $servicesMatch=Get-Wmiobject win32_service -ComputerName $computername|?{$_.StartName -like "*$searchForAccount*"}|select Name,DisplayName,StartName,StartMode,Status|ft -AutoSize|Out-String

        if($servicesMatch){
            $results+="Services 'Run-As' account $searchForAccount found:`r`n";
            $results+="$servicesMatch";
            }
        return $results;
    }

function detectAntivirus($computername) {  
            $wmiQuery = "SELECT * FROM AntiVirusProduct"                  
            try{
                $antivirus = Get-WmiObject -ComputerName $computername -Namespace "root\SecurityCenter2" -Query $wmiQuery @psboundparameters -ErrorVariable myError -ErrorAction SilentlyContinue             
                }catch{
                    $antivirus=$false;
                    }
            if($antivirus){
                return $antivirus.displayName            
                }else{
                            try{
                                write-host "Unable to detect antivirus in namespace root\SecurityCenter2. Now querying AppWiz.cpl ..."
                                $antivirus=Get-CimInstance -ClassName win32_InstalledWin32Program -ComputerName $computername | ?{$_.Name -match $expectedAntivirusNames}|%{"$($_.Name)"}
                                }
                            catch{
                                $antivirus=$false
                                }
                            
                            if (!$antivirus){
                                try{
                                    write-host "Unable to detect antivirus in AppWiz. Now querying registry ..."
                                    $results = @()
                                    try{
                                        Get-Service -ComputerName $computername -Name RemoteRegistry | Start-Service
                                        $hive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $computername)
                                        }catch{
                                            write-host "unable to open remote registry"
                                            $hive=$false
                                            }
                                        if ($hive){
                                            $regPathList = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
                                                           "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
                                            foreach($regPath in $regPathList) {
                                                if($key = $hive.OpenSubKey($regPath)) {
                                                    if($subkeyNames = $key.GetSubKeyNames()) {
                                                        foreach($subkeyName in $subkeyNames) {
                                                            $productKey = $key.OpenSubKey($subkeyName)
                                                            $productName = $productKey.GetValue("DisplayName")
                                                            $productVersion = $productKey.GetValue("DisplayVersion")
                                                            $productComments = $productKey.GetValue("Comments")
                                                            if (!$productName){$productName="";}
                                                            if (!$productComments){$productComments="";}
                                                            if(($productName.ToLower() -match $keywords) -OR ($productComments.ToLower() -match $keywords)) {
                                                                $resultObj = [PSCustomObject]@{
                                                                    Product = $productName
                                                                    Version = $productVersion
                                                                }
                                                                $results += $resultObj
                                                            }
                                                        }
                                                    }
                                                }
                                                $key.Close()
                                            }
                                        }
                                    write-host $results| ft -au
                                    $antivirus=$results
                                    }
                                catch{
                                    write-host "Unable to detect antivirus in registry ..."
                                    $antivirus=$false
                                    }
                                }
                            return $antivirus
                            }

                }

function getContainer{
        param(
            $computername=$env:computername
            )   
        if ((gwmi win32_computersystem -ComputerName $computername).partofdomain -eq $true){            
                if (!(Get-Module ActiveDirectory)){
                    try {
                        $voidOutput=[void](Import-Module ServerManager -ErrorAction SilentlyContinue)
                        $voidOutput=[void](Add-WindowsFeature RSAT-AD-PowerShell -Confirm:$false);
                
                        ######## Error ######
                        #Error on some systems
                        #Add-WindowsFeature : The target of the specified cmdlet cannot be a Windows client-based operating system.
                        #At line:1 char:1
                        #+ Add-WindowsFeature RSAT-AD-PowerShell -Confirm:$false
                        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        #    + CategoryInfo          : DeviceError: (localhost:String) [Install-WindowsFeature], Exception
                        #    + FullyQualifiedErrorId : WindowsClient_NotSupported,Microsoft.Windows.ServerManager.Commands.AddWindowsFeatureCommand
                        #
                        #Get-ADComputer : Unable to contact the server. This may be because this server does not exist, it is currently down,
                        #or it does not have the Active Directory Web Services running.
                        #At line:1 char:1
                        #+ Get-ADComputer $env:computername
                        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        #    + CategoryInfo          : ResourceUnavailable: (SHERVER007:ADComputer) [Get-ADComputer], ADServerDownException
                        #    + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADComputer
                        #
                        # 
                        # Resolution:
                        # Get-ADComputer $env:computername -Server $DomainController -Credential (get-credential)
                        ######################

                        $voidOutput=[void](Import-Module ActiveDirectory -ErrorAction SilentlyContinue)
                        }catch{Continue;}

                    ##### Error when executing remotely ###
                    #WARNING: Error initializing default drive: 'Unable to contact the server. This may be because this server does not exist,
                    #it is currently down, or it does not have the Active Directory Web Services running.'.

                    }
                [string]$nearestDc=(Get-ADDomainController -Discover).HostName
                $result=[string](Get-ADComputer $computername -Server $nearestDc).DistinguishedName -Replace "^CN=[^,]+,",'' 
                }else{
                    $result="This computer is not domain joined."
                    }
                return $result
        }

# Function obtained from 
# 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 obtained from 
# 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){
    if (!(get-command psexec)){
        # Install Chocolatey
        if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
            Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
            }
        try {            
            choco install sysinternals -y;
            # 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;
            }
            catch{
                write-warning 'Unable to install Systernals.';
                $success=$false;
                }
        }
    return $success;
}

function Check-NetConnection($server, $port) {
    $session = New-Object System.Net.Sockets.TcpClient;
    try {
        $session.Connect($server, $port);
        $true;
    } catch {
        $false;
    } finally {
        $session.Close();
    }
}

function systemsDiscovery($computers,$csvOutput){
        # Global variables
        $GLOBAL:computerObjects=@()  
        $GLOBAL:noScans=@()
        $scanResult=@()
        foreach($computer in $computers){
            $GLOBAL:winRmAvailable=$false
            $GLOBAL:rpcAvailable=$false
            $pingSuccess=$false
            $serverName=$computer.Name
            write-host "Checking $serverName ..."
            $GLOBAL:container=$server.container
            $pingSuccess=Test-Connection $serverName -Count 1 -Quiet -ErrorAction SilentlyContinue
            $winRmAvailable=.{
                try{
                    Test-WSMan -ComputerName $serverName -ErrorAction Stop
                    return $true
                    }
                catch{                
                    try{
                        if(enableWinRm $serverName){return $true}
                        }
                    catch{
                        return $false
                        }                
                    }
                }
            $rpcAvailable=.{
                try {
                    gwmi win32_operatingsystem -ComputerName $serverName -ea stop
                    return $true;
                    }
                catch {
                    return $false;
                    }            
                }

            if($pingSuccess -and $winRmAvailable -and $rpcAvailable){
                $scanResult=checkServer $serverName;
                if($scanResult){
                    $computerObjects+=,$scanResult
                    }
                }else{
                    $noScans+=,[pscustomobject]@{Name="$serverName`r`npingable: $pingSuccess`r`nwinRMReachable: $winRmAvailable`r`nrpcAvailable: $rpcAvailable" }
                    write-warning "$servername is not accessible via WinRM to perform the scan."
                    }
        }

        try{            
            $computerObjects|Export-Csv -NoTypeInformation -Path $csvOutput
            }
        catch{
            killPidLockedFile -filename $csvOutput | out-null
            $computerObjects|Export-Csv -NoTypeInformation -Path $csvOutput
            }
        finally{
            #write-host "These servers were not reachable via WinRM: $noScans"
            $noScans|Export-Csv -NoTypeInformation -Append -Force -Path $csvOutput
            write-host "Results have now been saved at: $csvOutput"
            }
}

function checkServer([string]$name="localhost"){
    # Initialize variables
    $computerObject=New-Object PSCustomObject
    $resourceAlerts=$false;
    $securityAlerts=$false;
    $storageIssues=$false;
    $ips="Unknown";
    $machineType="Unknown";
    $machineModel="Unknown";
    $hyperVHostname="N/A";
    $serial="Unknown";
    $os="Unknown";
    $cpu=$cpuLoad="Unknown";
    $memory="Unknown";
    $volumes="Unknown";
    $lastUpdate="Unknown";
    $antivirusName="Unknown";
    $activationStatus="Unknown";
    $cpuObject=@();
    $servicesWithDefaultAdministrator="";
    $winDefenderStatus="";
    $rdpSecurity="";
    $spectreVulnerability="";
    $otherVulnerabilities="";
    $localAdministrators="";
    $lastLogonUsers="";
    $currentlyLogonUsers="";
    $topProcesses="";
    $memoryUsagePerUser="";    
    $container="";
    $certs="";
    $installedApps="";
    $autoStartServices="";
    $smbShares="";
    $kbs="";
    $gateway="";
    $publicIP="";
    $dns="";
    $pageFiles="";
    $currentSendReceive=@();
    $internetDownloadSpeed=0;
    $osArchitecture="";
    $compatibilityForMigrationEnabled="N/A"
    $vmGeneration="N/A"
    $vmStorage="N/A"
    $hasPageVolume=$false
    $commonPortsInUse=""

    try{
        $ips=([Net.DNS]::GetHostEntry($name).AddressList.IPAddressToString|out-string).Trim();
        $gateway=((Get-WmiObject -Class Win32_IP4RouteTable -ComputerName $name|where{ $_.destination -eq '0.0.0.0' -and $_.mask -eq '0.0.0.0'}).nexthop|out-string).Trim()
        #$dns=(Invoke-command -ComputerName $name -ScriptBlock {Get-DnsClientServerAddress| Select-Object –ExpandProperty ServerAddresses|sort-object -Unique}|out-string).Trim()
        $dns=.{
            $x=Get-WmiObject Win32_NetworkAdapterConfiguration -computername $name | select DNSServerSearchOrder|out-string
            $x=$x -split '\r?\n'
            return $x|?{$_ -match "\b(?:\d{1,3}\.){3}\d{1,3}"}
            }
        $publicIP=(Invoke-command -ComputerName $name -ScriptBlock {(Invoke-WebRequest -URI ("ifconfig.me/ip") -UseBasicParsing).Content}|out-string).Trim()
        #$internetDownloadSpeed=internetSpeedTest
        #write-host $internetDownloadSpeed;

        $serial=(Get-WMIObject -Class Win32_BIOS -ComputerName $name).SerialNumber
        write-host $serial
        $cpuObject = Get-WmiObject win32_processor -ComputerName $name -ea stop |select Name,NumberOfCores,LoadPercentage
        $cpu=($cpuObject|select Name,NumberOfCores|Out-String).Trim()
        write-host $cpu
        $cpuLoad = ($cpuObject|  Measure-Object -property LoadPercentage -Average | Select @{Name="CurrentLoad";Expression={"{0:N2} %" -f ($_.Average)}}).CurrentLoad
        write-host $cpuLoad
        #$osArchitecture=(get-wmiobject win32_computersystem -computer $name).SystemType # shows: 32-bit or 64-bit
        #write-host $osArchitecture
        $osAndMemory = gwmi -Class win32_operatingsystem  -ComputerName $name -ea SilentlyContinue | Select-Object -First 15 | 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;
            }
        write-host "$os`r`n$memory`r`n$memoryUtilization"
        
        #$allVolumes=gwmi -Class win32_volume -ComputerName $name -Filter "DriveType!=5 " -ea stop| ?{$_.DriveLetter -ne $null}
        $allVolumes=gwmi -Class win32_volume -ComputerName $name -Filter 'DriveType=3 AND DriveLetter IS NOT NULL' -ea stop
        $storageIssues=.{
            $allVolumes|%{if((($_.Capacity-$_.FreeSpace) / $_.Capacity) -ge $threshold){return $true}} # flag machines that have volumes exceeding a certain threshold
            for ($i=0; $i -lt $mustHaveDriveLetters.length;$i++){
                if(!($mustHaveDriveLetters[$i].ToString().ToUpper()+":" -in $allVolumes.DriveLetter)){
                    return $true
                    }
                } # flag machines that don't have a certain drive letters
            }
        $hasPageVolume=if($expectedPageDrive.ToString().ToUpper()+":" -in $allVolumes.DriveLetter){$true}else{$false}
        $volumes = (($allVolumes|Select-object `
                        @{Name="Letter";Expression={($_.DriveLetter)[0]}},`
                        @{Name="Label";Expression={$_.Label}},`
                        @{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}},`
                        @{Name = "Available"; Expression = {"{0:N2} GiB" -f ($_.FreeSpace/1073741824)}},`
                        @{Name = "Utilization"; Expression = {"{0:N2} %" -f  ((($_.Capacity-$_.FreeSpace) / $_.Capacity)*100)}},`
                        #@{Name = "diskBrand"; Expression = {(($_).getrelated().getrelated('Win32_DiskDrive')).caption}},`
                        # List disks brand and models
                        #((gwmi -Class Win32_logicaldisk).getrelated('Win32_DiskPartition').getrelated('Win32_DiskDrive')).caption.replace("USB Device","")
                        @{Name = "diskSpeed"; Expression = {getDiskIops -computername $name -driveLetter $_.DriveLetter}}`
                        )|ft| Out-String).Trim()
        write-host $volumes

        $pageFiles=(Get-CimInstance -ComputerName $name -Class Win32_PageFileUsage|%{"$($_.Name) Allocated: $($_.AllocatedBaseSize) MB CurrentUsage: $($_.CurrentUsage) MB PeakUsage: $($_.PeakUsage) MB"}|out-string).Trim()
        write-host $pageFiles
        $activationStatus=Get-ActivationStatus -DNSHostName $name
        write-host $activationStatus
        #[string]$lastUpdate=gwmi win32_quickfixengineering |select HotFixID,InstalledOn|Select-Object -first 1
        $lastUpdate=Invoke-command -ComputerName $name -ScriptBlock {(Get-HotFix | Measure-Object InstalledOn -Maximum).Maximum.ToString().Trim()}
        write-host "Last update: $lastUpdate"
        #$kbs=(get-hotfix -ComputerName $name|select InstalledOn,HotFixID,InstalledBy|sort-object -Property InstalledOn -Desc|ft -AutoSize|out-string).Trim() #|Select-Object -first 10
        #write-host $kbs
        $antivirusName=detectAntivirus -computername $name
        write-host $antivirusName
        
        $servicesWithDefaultAdministrator=if((gwmi win32_computersystem -ComputerName $name).partofdomain -eq $true){
                                                checkServiceAccount -computername $name -searchForAccount $defaultDomainAdmin
                                                }else{
                                                    "Unable to check service account.";
                                                    }
        write-host "Services with default Admin Account:`r`n$servicesWithDefaultAdministrator"
        $localAdministrators=Invoke-command -ComputerName $name -ScriptBlock {
            (net localgroup administrators | where {$_ -AND $_ -notmatch "command completed successfully"}|select -skip 4|Out-String).Trim()
            }     
        write-host $localAdministrators
        $winDefenderStatus=.{
            $remoteSherver=$name 
            if (!$latestVersion){getLatestWinDefenderVersion}
            $result="";
                try{                   
                    $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 $releaseDateLocalTime
                    $daysDifference=$timeDifference.Days
                    write-host "It has been $daysDifference day(s) between Last Update: $lastUpdated and New Release: $releaseDate";
                    if ($actualVersion -ne $latestVersion){
                        $result+="Malware Antivirus Signature version $actualVersion on this system does not match the Latest version of $latestVersion";
                        #Update-MPSignature;
                        }
                    else{
                        $result+= "Excellent! Malware Antivirus Definition $actualVersion on this computer matches the Microsoft release.";                    
                    }       
                }
                catch{
                    $result+="Windows Defender is NOT enabled on this system.";
                }      
            return $result;
        }
        write-host $winDefenderStatus
        $rdpSecurity=(checkRDP -remoteSherver $name|Out-String).Trim();
        write-host $rdpSecurity
        $spectreVulnerability=(checkSpectreVulnerability -remoteSherver $name|Out-String).Trim();
        write-host $spectreVulnerability
        $otherVulnerabilities=(checkOtherVulnerabilities -remoteSherver $name|Out-String).Trim();    
        write-host $otherVulnerabilities
        
        $recentLogons=.{
                            $lastLogons=@();
                            $usersAndEmails=@();                       
                            $logonEvents=Get-WmiObject -Class Win32_UserProfile -ComputerName $name | Sort-Object -Property LastUseTime -Descending |?{$_.SID -notmatch "(S-1-5-18|S-1-5-19|S-1-5-20)" -and $_.SID -notlike "S-1-5-21*500"}
                            if ($logonEvents){
                            $usersCount=$logonEvents.Length;
                            for ($i=0;$i -lt $usersCount; $i++){                
                                $sid=$logonEvents[$i].SID
                                try{
                                    $user=(New-Object System.Security.Principal.SecurityIdentifier($sid)).Translate([System.Security.Principal.NTAccount]).Value
                                    $user=.{
                                        $domain=.{[void]($user -match "^([A-Za-z0-9]*)\\");return $matches[1]}
                                        $serverHost=.{if($name -match "\."){[void]($name -match "^([A-Za-z0-9]*)\.");return $matches[1]}else{return $name}}
                                        if ($domain -ne $name){
                                            [void]($user -match "([A-Za-z0-9]*)$");
                                            return $matches[1]
                                            }
                                        }
                                    }
                                catch{
                                    #$user=$logonEvents[$i].SID
                                    }
                                $logonTime=([WMI]'').ConvertToDateTime($logonEvents[$i].LastUseTime)                      
                                $count=$i+1;                 
                                $lastLogons+=,@($user,$logonTime);
                                }
                            }        
                            
                            # convert 2D Array to Hash Table while removing any duplicate keys
                            function arrayToHash($arr){
                                    $hash = new-object System.Collections.Hashtable
                                    $arr=$arr|sort -Descending # Higher (more recent) values on top
                                    for ( $i = 0; $i -lt $arr.Length; $i++ ) {
                                      try{$hash.Add($arr[$i][0],$arr[$i][1])}catch{}
                                    }
                                    return $hash
                                }
                            
                            function hashToArray($hash){
                                $arr=@();
                                [string[]] $arr = ($hash | Out-String -Stream) -ne '' | select -Skip 2
                                return $arr
                                }
                            
                            $hash=ArrayToHash $lastLogons
                            $arr=HashToArray $hash                            

                            foreach ($user in $arr){
                                    try{
                                        $username=.{[void]($user -match "^(\w*)\s");$matches[1]}
                                        $logonTime=.{[void]($user -match "\s([^\s].*)$");$matches[1]}
                                        $userObject=get-aduser -Identity $username -Properties * -ErrorAction Stop
                                        $emailAddress=$userObject.mail
                                        $firstname=$userObject.GivenName
                                        $lastname=$userObject.Surname
                                        #write-host "$firstName $lastName $emailAddress"
                                        $usersAndEmails+=,($username,"$firstName $lastName",$logonTime,$emailAddress)
                                        }
                                    catch{
                                        #write-host "$user is invalid."
                                        }
                                    }
                            return $usersAndEmails|%{"$($_[1]): $($_[0]) $(($_[2]|out-string).trim()) $($_[3])"}
                        }|out-string
        write-host $recentLogons
        
        $commonPortsInUse=(invoke-command -computername $name -scriptblock {   
                                                                    $limitPorts=10000 
                                                                    $columns=5;
                                                                    $netstat=netstat -aon
                                                                    $commonPortsInUse=$netstat|%{
                                                                                                [void]($_ -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:(\d{1,5})");
                                                                                                try{[int]$port=$matches[1]}catch{$port=0}
                                                                                                if($port -gt 0 -and $port -lt $limitPorts){$port}
                                                                                                }|select-object -Unique|sort
                                                                    return $commonPortsInUse|Format-Wide {$_} -Column $columns -force                                                               
                                                                    }|out-string) -replace '\s{8}',''

        <#
        $currentlyLogonUsers=(.{
                try{
                    $processinfo = @(Get-WmiObject -class win32_process -ComputerName $name -EA Stop)
                        if ($processinfo)
                        {  
                            $processinfo | Foreach-Object {$_.GetOwner().User} |
                            Where-Object {$_ -ne "NETWORK SERVICE" -and $_ -ne "LOCAL SERVICE" -and $_ -ne "SYSTEM"} |
                            Sort-Object -Unique
                        }
                    }
                catch
                    {
                        "Cannot find any processes running on $name"
                    }
            }|out-string).trim()
        write-host "Current logons:`r`n$currentlyLogonUsers"
        #>

        <#
        $currentRdpUsers=(.{ 
                    # Local variables
                    $users=@();
                    $usersAndEmails=@();
                    $queryResults = qwinsta /server:$name | foreach { (($_.trim() -replace "\s+",","))} | ConvertFrom-Csv 
     
                    # parse the results
                    ForEach ($queryResult in $queryResults) { 
                        $rdpUser = $queryResult.USERNAME 
                        $sessionLabel = $queryResult.SESSIONNAME 
       
                        # Obtain a list of users
                        If (($rdpUser -match "[a-zA-Z]") -and ($rdpUser -ne $NULL)) {  
                            $users+=,$rdpUser
                            }elseif($sessionLabel -match "[a-zA-Z]"-and ($sessionLabel -ne $NULL)){
                                $users+=,$sessionLabel
                                }
                        } 

                    foreach ($user in $users){
                        try{
                            $userObject=get-aduser -Identity $user -Properties * -ErrorAction Stop
                            $emailAddress=if($userObject.mail){$userObject.mail}else{"None"}
                            $firstname=$userObject.GivenName
                            $lastname=$userObject.Surname
                            #write-host "$firstName $lastName`: $emailAddress"
                            $usersAndEmails+=,"$firstName $lastName`: $emailAddress"
                            }
                        catch{
                            #write-host "$user is invalid."
                            }

                        }
                    return $usersAndEmails           
                }).toString().Trim()
        
        #>

        #$topProcesses=(get-process|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|ft -AutoSize| Out-String).Trim();
        $topProcesses=(get-process -ComputerName $name|Group-Object -Property ProcessName | `
                        Select Name, @{N='Memory (MB)';E={[math]::Round((($_.Group|Measure-Object WorkingSet -Sum).Sum / 1MB),2)}} | `
                        Sort-Object -Property "Memory (MB)" -Descending|Select-Object -First 10 |ft| Out-String).Trim();
        write-host "Top processes:`r`n$topProcesses"
        
        $memoryUsagePerUser=(.{
                                $allProcesses=get-wmiobject win32_process -ComputerName $name
                                $workingSets=$allProcesses |select-object @{N='User';E={$user=$_.getowner().User;if($user){$user}else{'SYSTEM'} }}, WorkingSetSize
                                $usersAndRamUsage=$workingSets | group user|Select-Object `
                                    @{N='Username';E={$_.Name}},   `
                                    @{N='Memory (MB)';E={[math]::Round(($_.Group.WorkingSetSize  | Measure-Object -Sum).Sum / 1Mb,2)  }} `
                                    |sort-object -property "Memory (MB)" -Descending|Select-Object -First 15;
                                return $usersAndRamUsage
                              }|ft|out-string).Trim()
        write-host "Top RAM users:`r`n$memoryUsagePerUser"
        
        $container=getContainer -computername $name   
        write-host "container: $container"
        $certObjects=Invoke-command -ComputerName $name -ScriptBlock {Get-ChildItem -Path cert:\LocalMachine\My}
            foreach ($cert in $certObjects){$certs+="$($cert.Thumbprint) $($cert.Subject)`r`n"}
        write-host $certs
        $smbShares=(Invoke-command -ComputerName $name -ScriptBlock {$rawString=net share;($rawString -split '\r?\n')[4..$($rawString.length-3)]}|out-string).Trim()
        write-host $smbShares
        
        #$autoStartServices=(Get-WmiObject -ComputerName $name -ClassName Win32_Service -Filter "StartMode='Auto'"|Format-Table Name,StartName,State -AutoSize|out-String).Trim()
        #write-host $autoStartServices
        #
        #$installedApps=Invoke-command -ComputerName $name -ScriptBlock {
        #            Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object InstallDate,DisplayName, DisplayVersion, Publisher `
        #             | Sort-Object -Property InstallDate -Descending |?{$_.DisplayName.length -gt 1 }|ft -AutoSize|out-string
        #        }
        #write-host "Installed Apps: $installedApps"
        #
        #$regexOU=".+?(?=,),(.*)\."            
        #[string]$disabledComputersOu=(Get-ADOrganizationalUnit -Filter 'Name -like "Disabled Computers"'|select DistinguishedName).DistinguishedName
        #$container=Get-ADComputer $env:computername | Move-ADObject -TargetPath $disabledComputersOu -WhatIf -PassThru | Out-String   
        
        if($cpuLoad -as [int] -ge $threshold -or
            $memoryUtilization -as [int] -ge $threshold -or            
            $storageIssues -eq $true
            ){$resourceAlerts=$true}
        if (
            $activationStatus -ne 'Licensed' -or
            $servicesWithDefaultAdministrator -ne '' -or
            $antivirusName -eq $false -or
            $rdpSecurity -match "fail" -or
            $spectreVulnerability -match "fail" -or
            $otherVulnerabilities -match "fail"
            ){$securityAlerts=$true}
        if(!($machineModel)){$machineModel="Unknown"};
        if(!($machineType)){$machineType="Unknown"};
        
        $machineModel=(Get-WmiObject -Class Win32_ComputerSystem -ComputerName $name -ea stop).Model
        write-host $machineModel
        switch -wildcard ($machineModel){
            "VMware*" {
                $machineType="VMWare Virtual Machine";
                $hyperVHostname="ESXi host has not been setup to pass the host's machine.id to guest. Hence, hostname cannot be retrieved from inside $env:computername";
                }
		    "Virtual Machine" {
                $machineType="Hyper-V Virtual Machine";
                $hyperVHostname=getHyperVHostname -guestVMName $name;
                if($hyperVHostname){
                    $vmInfo=invoke-command -ComputerName $hyperVHostname -ScriptBlock{
                                                                        param($name)                                                                        
                                                                        $compatForMigrateEnabled=(Get-VMProcessor "$name*").compatibilityForMigrationEnabled;
                                                                        $guestVM=get-vm "$name*"
                                                                        $generation=$guestVm.Generation
                                                                        $storage=$guestVm.HardDrives|select ControllerType,Path
                                                                        #$storageController=$storage.ControllerType
                                                                        #$storagePath=$storage.Path
                                                                        return @($compatForMigrateEnabled,$generation,$storage)
                                                                        } -args $name
                    $compatibilityForMigrationEnabled=$vmInfo[0].ToString()
                    $vmGeneration=$vmInfo[1].ToString()
                    $vmStorage=($vminfo[2]|Out-String).trim()
                    }else{
                        $compatibilityForMigrationEnabled="Unknown"
                        }
                }
		    "*HVM*" {$machineType="AWS Virtual Machine";$hyperVHostname="N/A";}
            "*.*" {$machineType="AWS Virtual Machine";$hyperVHostname="N/A";}	        
            default {$machineType="Unknown";$hyperVHostname="Unknown";}
		}
        
        if(!($osAndMemory)){$os=$memory=$memoryUtilization="Unknown";}
        if(!($ips)){$ips="Unknown";}
        if(!($cpuObject)){$cpu=$cpuLoad="Unknown";}
        if(!($volumes)){$volumes="Unknown";}
        if(!($serial)){$serial="Unknown"}
        if(!($certs)){$certs="None"}
        if(!($servicesWithDefaultAdministrator)){$servicesWithDefaultAdministrator="None"}        
        
        }
    catch{
        Write-Host $_.ScriptStackTrace;
        continue;
        }
    finally{            
        $computerObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $name
        $computerObject | Add-Member -MemberType NoteProperty -Name "resourceAlerts" -Value $resourceAlerts
        $computerObject | Add-Member -MemberType NoteProperty -Name "securityAlerts" -Value $securityAlerts
        $computerObject | Add-Member -MemberType NoteProperty -Name "hasPageVolume" -Value $hasPageVolume
        $computerObject | Add-Member -MemberType NoteProperty -Name "ips" -Value $ips
        $computerObject | Add-Member -MemberType NoteProperty -Name "gateway" -Value $gateway
        $computerObject | Add-Member -MemberType NoteProperty -Name "dns" -Value $dns
        $computerObject | Add-Member -MemberType NoteProperty -Name "publicIP" -Value $publicIP
        $computerObject | Add-Member -MemberType NoteProperty -Name "commonPortsInUse" -Value $commonPortsInUse
        #$computerObject | Add-Member -MemberType NoteProperty -Name "internetDownloadSpeed" -Value $internetDownloadSpeed
        $computerObject | Add-Member -MemberType NoteProperty -Name "hyperVHostname" $hyperVHostname
        $computerObject | Add-Member -MemberType NoteProperty -Name "machineModel" -Value $machineModel
        $computerObject | Add-Member -MemberType NoteProperty -Name "serial" -Value $serial
        $computerObject | Add-Member -MemberType NoteProperty -Name "os" -Value $os
        #$computerObject | Add-Member -MemberType NoteProperty -Name "osArchitecture" -Value $osArchitecture
        $computerObject | Add-Member -MemberType NoteProperty -Name "activationStatus" -Value $activationStatus
        $computerObject | Add-Member -MemberType NoteProperty -Name "cpu" -Value $cpu
        $computerObject | Add-Member -MemberType NoteProperty -Name "cpuLoad" -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 "topProcesses" -Value $topProcesses
        $computerObject | Add-Member -MemberType NoteProperty -Name "memoryUsagePerUser" -Value $memoryUsagePerUser      
        $computerObject | Add-Member -MemberType NoteProperty -Name "volumes" -Value $volumes
        $computerObject | Add-Member -MemberType NoteProperty -Name "pageFiles" -Value $pageFiles
        $computerObject | Add-Member -MemberType NoteProperty -Name "container" -Value $container
        $computerObject | Add-Member -MemberType NoteProperty -Name "antivirus" -Value $antivirusName
        $computerObject | Add-Member -MemberType NoteProperty -Name "localAdministrators" -Value $localAdministrators
        $computerObject | Add-Member -MemberType NoteProperty -Name "servicesWithDefaultAdministrator" -Value $servicesWithDefaultAdministrator
        $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 | Add-Member -MemberType NoteProperty -Name "currentlyLogonUsers" -Value $currentlyLogonUsers
        $computerObject | Add-Member -MemberType NoteProperty -Name "certs" -Value $certs
        $computerObject | Add-Member -MemberType NoteProperty -Name "smbShares" -Value $smbShares
        #$computerObject | Add-Member -MemberType NoteProperty -Name "autoStartServices" -Value $autoStartServices
        #$computerObject | Add-Member -MemberType NoteProperty -Name "installedApps" -Value $installedApps;
        $computerObject | Add-Member -MemberType NoteProperty -Name "lastUpdate" -Value $lastUpdate
        #$computerObject | Add-Member -MemberType NoteProperty -Name "kbs" -Value $kbs
        $computerObject | Add-Member -MemberType NoteProperty -Name "recentLogons" -Value $recentLogons;
        #$computerObject | Add-Member -MemberType NoteProperty -Name "currentRdpUsers" -Value $currentRdpUsers;
        $computerObject | Add-Member -MemberType NoteProperty -Name "machineType" -Value $machineType
        $computerObject | Add-Member -MemberType NoteProperty -Name "compatibilityForMigrationEnabled" -Value $compatibilityForMigrationEnabled
        $computerObject | Add-Member -MemberType NoteProperty -Name "vmGeneration" $vmGeneration
        $computerObject | Add-Member -MemberType NoteProperty -Name "vmStorage" $vmStorage
             
        }

    write-host "Finished scanning $name"
    return $computerObject;
    #$GLOBAL:computerObjects+=,$computerObject
}

function convertCsvToHtml($csvFile,$outFile){
    # Source code adapted from https://learn.microsoft.com/en-us/archive/blogs/brandev/powershell-quick-html-reports-using-css-stylesheet
    $css = @"
    <style>
    h1, 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>
"@

    Import-CSV $csvFile -Delimiter ',' | ConvertTo-Html -Head $css -Body "<h1>Servers Inventory</h1>`n<h5>Generated on $(Get-Date)</h5>" | Out-File $outFile
}

systemsDiscovery -computers $servers -csvOutput $csvOutput;
convertCsvToHtml $csvOutput $htmlOutput
<# Systems-Inventory.ps1
Version: 0.03 -- deprecated 12/24/2019

Purpose: to generate a CSV spreadsheet with information about servers on the domain
1. Query Active Directory for a list of server names
2. Distinguish between servers and non-server machines
3. Probe each server for its general information (IPs, Machine Type, CPU, Memory, Storage, Last Update, Antivirus, etc.)
4. 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
#>

<#
################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
 
# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
 
# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
   {
   # We are running "as Administrator" - so change the title and background color to indicate this
   $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
   $Host.UI.RawUI.BackgroundColor = "Black"
   clear-host
   }
else
   {
   # We are not running "as Administrator" - so relaunch as administrator
   
   # Create a new process object that starts PowerShell
   $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
   
   # Specify the current script path and name as a parameter
   $newProcess.Arguments = $myInvocation.MyCommand.Definition;
   
   # Indicate that the process should be elevated
   $newProcess.Verb = "runas";
   
   # Start the new process
   [System.Diagnostics.Process]::Start($newProcess);
   
   # Exit from the current, unelevated, process
   exit
   }
 
Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################
#>

# 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
$regexComputerNames="(?<=CN=)(.*?),(OU|CN)=([^,]+)"
if(!(get-command dsquery -InformationAction SilentlyContinue)){
	$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
	$newProcess.Arguments="Get-WindowsCapability -Online |? Name -like 'Rsat.ActiveDirectory.DS-LDS.Tools*'|Add-WindowsCapability -Online; pause"
	$newProcess.Verb = "runas";
	[System.Diagnostics.Process]::Start($newProcess);
	sleep 20;
	}

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

# Collect computer names (non servers) and their containers
$computers=dsquery * -Filter "(&(objectCategory=computer)(!operatingSystem=*server*))" -limit 10| %{[void]($_ -match $regexComputerNames);$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
$serversExport="$scriptPath`\serversInventory.csv"
$computersExport="$scriptPath`\computersInventory.csv"

# 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 checkServiceAccountRemote($searchAccount,$sherver="localhost"){

    # 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 
# 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

        $temp=$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

        $temp=$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{
        param(
        [string]$name="localhost"
        )        
    $computerObject=New-Object PSObject
    $ips="Unknown";
    $machineType="Unknown";
    $machineModel="Unknown";
    $serial="Unknown";
    $os="Unknown";
    $cpu=$cpuLoad="Unknown";
    $memory="Unknown";
    $volumes="Unknown";
    $lastUpdate="Unknown";
    $antivirusName="Unknown";
    $activationStatus="Unknown";
    $cpuObject=@();
    $servicesWithDefaultAdministrator="";
    $winDefenderStatus="";
    $rdpSecurity="";
    $spectreVulnerability="";
    $otherVulnerabilities="";
    $localAdministrators="";
    $lastLogonUsers="";
    $lastLogonTimes="";
    $topProcesses="";
    $memoryUsagePerUser="";

          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
            $serial=(Get-WMIObject -Class Win32_BIOS -ComputerName $name).SerialNumber
            $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="Label";Expression={$_.Label}},@{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
            #[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()
            
            $servicesWithDefaultAdministrator=if((gwmi win32_computersystem).partofdomain -eq $true){
                                                                                                        checkServiceAccount $defaultDomainAdmin|Out-String|%{$_.Trim()} 
                                                                                                            }else{
                                                                                                                checkServiceAccount Administrator|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."}
            $lastLogonEvents=Get-WmiObject -Class Win32_UserProfile -ComputerName $name | Sort-Object -Property LastUseTime -Descending |?{$_.SID -notmatch "(S-1-5-18|S-1-5-19|S-1-5-20)"} | Select-Object -First 10
            if ($lastLogonEvents){
                $lastLogonUsers=$lastLogonEvents|%{$user=(New-Object System.Security.Principal.SecurityIdentifier($_.SID)).Translate([System.Security.Principal.NTAccount]).Value;if($user){$user}}|Out-String
                $lastLogonTimes=$lastLogonEvents|%{([WMI]'').ConvertToDateTime($_.LastUseTime)}|Out-String
                }
            $topProcesses=get-process -computername $name|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|Out-String;
            $memoryUsagePerUser= get-wmiobject win32_process -computername $name|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|Out-String;
            }
          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 "serial" -Value $serial
                $computerObject | Add-Member -MemberType NoteProperty -Name "os" -Value $os
                $computerObject | Add-Member -MemberType NoteProperty -Name "activationStatus" -Value $activationStatus
                $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 "topProcesses" -Value $topProcesses
                $computerObject | Add-Member -MemberType NoteProperty -Name "memoryUsagePerUser" -Value $memoryUsagePerUser            
                $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 "servicesWithDefaultAdministrator" -Value $servicesWithDefaultAdministrator
                $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 | Add-Member -MemberType NoteProperty -Name "lastLogonUsers" -Value $lastLogonUsers;
                $computerObject | Add-Member -MemberType NoteProperty -Name "lastLogonTimes" -Value $lastLogonTimes;
            }
            $computerObject;
            $GLOBAL:computerObjects+=,$computerObject
}

# Function obtained from 
# 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{
    
    function inventoryServers{
        # Init output as an empty array
        $GLOBAL:computerObjects=@()         
        foreach($server in $servers){
            $serverName=$server.Name
            "Scanning $serverName ..."
            $GLOBAL:container=$server.container
            $GLOBAL:winRmAvailable=Test-NetConnection -CommonTCPPort WINRM -ComputerName $serverName -InformationLevel Quiet -ErrorAction SilentlyContinue
            if(!($winRmAvailable)){$GLOBAL:winRmAvailable=enableWinRm $serverName}
            checkServer $serverName
        }
        $computerObjects|Export-Csv -Path $serversExport -NoTypeInformation
        "Results have now been saved at: $serversExport"
    }

    function inventoryComputers{
        # Init output as an empty array
        $GLOBAL:computerObjects=@()         
        foreach($computer in $computers){
            $computerName=$computer.Name
            "Scanning $computerName ..."
            $GLOBAL:container=$computer.container
            $GLOBAL:winRmAvailable=Test-NetConnection -CommonTCPPort WINRM -ComputerName $computerName -InformationLevel Quiet -ErrorAction SilentlyContinue
            if(!($winRmAvailable)){$GLOBAL:winRmAvailable=enableWinRm $computerName}
            checkServer $computerName
        }
        $computerObjects|Export-Csv -Path $computersExport -NoTypeInformation
        "Results have now been saved at: $computersExport"
    }

    $prompt="Please type 'servers' or 'computers' (non-servers) to proceed. 'Cancel' to exit"    
    do{
        $userInput=Read-Host -Prompt $prompt;
        if ($userInput -match "servers*"){"Servers!";inventoryServers;}
        if ($userInput -match "computers*"){"Computers (non-servers)!";inventoryComputers;}
    }while (($userInput -notmatch "(servers|computers)") -AND ($userInput -notmatch "(quit|cancel|exit)"))
}

systemsDiscovery;