PowerShell: Search Windows Event Logs

# searchWindowsEventsLog.ps1

$computername=$env:computername
$logType='Security'
$eventId=4732
$daysBack=365
$limit=9999
$messageLike="*Remote Desktop Users*"

function searchWindowsEvents{
    param(
        $computername=$env:computername
        $logType='Security'
        $eventId=4732
        $daysBack=365
        $limit=9999
        $messageLike="*Remote Desktop Users*"    
    )    

    $filter=@{
        LogName=$logType
        ID=$eventId
        StartTime=[datetime]::Now.AddDays(-$daysBack)
    }
    
    $events=Get-WinEvent -FilterHashTable $filter -ComputerName $computername -EA Ignore|select -first $limit
    $events|?{$_.Message -like $messageLike}
}

searchWindowsEvents $computername $logType $eventId $daysBack $limit $messageLike

PowerShell: Increase CPU Count or Memory of VMs via Virtual Machine Manager

# IncreaseCpuandRamViaVMM.ps1

# User Input Variables
$vmNames=@(
    'TESTWINDOWS',
    'TESTWINDOWS2'
)
$vmmServer=$env:computername
$setCpuCount=8
$setDynamicMemory=$false
$dynamicMemoryMinimumGB='2GB'
$dynamicMemoryMaximumGB='16GB'

# Get all VMs beloging to specific cloud(s) - this is to dynamically configure all VMs within a virtual cloud
# $vms=Get-SCVirtualMachine -VMMServer $vmmServer|?{$_.Cloud.Name -in $cloudNames}
$dynamicMemoryMinimumMB=$dynamicMemoryMinimumGB/1MB
$dynamicMemoryMaximumMB=$dynamicMemoryMaximumGB/1MB
$results=@()
foreach ($vm in $vms){
    $vmName=$vm.Name
    $previousCpuCount=$vm.CPUCount
    write-host "Upgrading $vmName..."
    if($vm.Status -eq 'Running'){
        Stop-SCVirtualMachine -VM $vm -Shutdown
    }    
    if($setCpuCount -and !$setDynamicMemory){
        if($vm.CPUCount -lt $setCpuCount){
            Set-SCVirtualMachine -VM $vm -RunAsSystem -CPUCount $setCpuCount -CPUExpectedUtilizationPercent 20
        }else{
            write-host "$($vm.Name) CPU Count is $previousCpuCount, which is already equal or higher than $setCpuCount"
        }
        $results+=@{
            vmName=$vmName;
            previousCpuCount=$previousCpuCount;
            currentCpuCount=$setCpuCount
            }
    }elseif($setCpuCount -and $setDynamicMemory){
        Set-SCVirtualMachine -VM $vm -RunAsSystem `
        -CPUCount $setCpuCount -CPUExpectedUtilizationPercent 20 `
        -DynamicMemoryEnabled $setDynamicMemory -DynamicMemoryBufferPercentage 20 -MemoryWeight 5000 `
        -MemoryMB $dynamicMemoryMinimumMB -DynamicMemoryMinimumMB $dynamicMemoryMinimumMB -DynamicMemoryMaximumMB $dynamicMemoryMaximumMB        
        $results+=@{
            vmName=$vmName;
            previousCpuCount=$previousCpuCount;
            currentCpuCount=$setCpuCount;
            StartupMemory=$dynamicMemoryMinimumMB;
            DynamicMemoryMinimumMB=$dynamicMemoryMinimumMB;
            DynamicMemoryMaximumMB=$dynamicMemoryMaximumMB
            }
    }elseif(!$setCpuCount -and $setDynamicMemory){
        Set-SCVirtualMachine -VM $vm -RunAsSystem `
                -DynamicMemoryEnabled $setDynamicMemory -DynamicMemoryBufferPercentage 20 -MemoryWeight 5000 `
                -MemoryMB $dynamicMemoryMinimumMB -DynamicMemoryMinimumMB $dynamicMemoryMinimumMB -DynamicMemoryMaximumMB $dynamicMemoryMaximumMB        
                $results+=@{
                    vmName=$vmName;
                    StartupMemory=$dynamicMemoryMinimumMB;
                    DynamicMemoryMinimumMB=$dynamicMemoryMinimumMB;
                    DynamicMemoryMaximumMB=$dynamicMemoryMaximumMB
                    }
    }else{    
        write-warning "User must input the `$setDynamicMemory and/or `$setCpuCount variables"
        break
    }
    if($vm.Status -eq 'PowerOff'){
        Start-SCVirtualMachine -VM $vm
    }    
}
write-host $($results|out-string).trim()

PowerShell: Virtual Machine Snapshots Report from VMM Servers

# vmSnapshotReport.ps1
# Requirements:
# - Credentials to access VMM Servers with Administrator role
# - Firewall open WinRM ports from jump host to VMM Servers

# Variables
$vmmServers=@(
    @{servername='LAX-VMM01.kimconnect.com';adminname='intranet\clusterReport';password=$env:kimconnectcom}
    @{servername='PHX-VMM01.kimconnect.net';adminname='intranet\clusterReport';password=$env:kimconnectnet}
)

# Email relay parameters
$emailFrom='admins@kimconnect.com'
$emailTo='admins@kimconnect.com'
$subject='Hyper-V Snapshots Report'
$smtpRelayServer='email-relay.kimconnect.com'
$emailDay='Monday'
$css="
    <style>
    .h1 {
        font-size: 18px;
        height: 40px;
        padding-top: 80px;
        margin: auto;
        text-align: center;
    }
    .h5 {
        font-size: 22px;
        text-align: center;
    }
    .th {text-align: center;}
    .table {
        padding:7px;
        border:#4e95f4 1px solid;
        background-color: white;
        margin-left: auto;
        margin-right: auto;
        width: 100%
        }
    .colgroup {}
    .th { background: #0046c3; color: #fff; padding: 5px 10px; }
    .td { font-size: 11px; padding: 5px 20px; color: #000;
            width: 1px;
            white-space: pre;
        }
    .tr { background: #b8d1f3;}
    .tr:nth-child(even) {
        background: #dae5f4;
        width: 1%;
        white-space: nowrap
    }
    .tr:nth-child(odd) {
        background: #b8d1f3;
        width: 1%;
        white-space: nowrap
    }
    </style>
"

$results=$null
foreach($vmmServer in $vmmServers){
    $servername=$vmmServer.servername
    $username=$vmmServer.adminName
    $password=convertto-securestring $vmmServer.password -AsPlainText -Force
    $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$password
    $pssession=new-pssession -computername $servername -credential $credential
    if($pssession.State -eq 'Opened'){
        $results+=invoke-command -session $pssession {Get-SCVMCheckpoint -vmmserver localhost}
        remove-pssession $pssession
    }else{
        write-warning "Unable to connect to $servername"
        break
    }
}
$report=$results|select @{name='VMMServer';e={$_.PSComputername}},VM,Name
if($null -ne $report){
    $currentReport=$report | ConvertTo-Html -Fragment | Out-String
    $currentReportHtml=$currentReport -replace '\<(?<item>\w+)\>', '<${item} class=''${item}''>'
    $emailContent='<html><head>'+$css+"</head><body><h5 class='h5'>$subject</h5>"+$currentReportHtml+'</body></html>'
    write-host "####################################################################################################`r`nVM Snapshots Report:`r`n####################################################################################################`r`n$(($report|out-string).trim())"
    $today=(get-date).DayOfWeek
    if ($today -eq $emailDay){
        Send-MailMessage -From $emailFrom `
        -To $emailTo `
        -Subject $subject `
        -Body $emailContent `
        -BodyAsHtml `
        -SmtpServer $smtpRelayServer
        write-host "$subject email has been sent to $emailTo with time stamp $((get-date|out-string).trim())"
    }
}

PowerShell: Set VM Dynamic Memory in Virtual Machine Manager

# setVmDynamicMemoryInVmm.ps1

# Optimize Dynamic RAM
$minGb='16GB'
$maxGb='32GB'
$startupGb='2GB'
$buffer=20
$memoryWeight=5000
$forcedRestart=$false
$vmmServer='localhost'

function getUnoptimizedMemoryVms{
    param(
        $minGb='16GB',
        $maxGb='1024GB',
        $vmmServer='localhost'
    )
    Import-Module -Name "virtualmachinemanager"
    $vms=Get-VM -vmmserver $vmmServer
    $targetVms=$vms|?{$($_.MemoryAssignedMB -ge $minGb/1MB -and $_.MemoryAssignedMB -le $maxGb/1MB) -and $($_.DynamicMemoryDemandMB -ne $_.MemoryAssignedMB)} # -and !$_.DynamicMemoryEnabled
    # Memory = startup memory
    # DynamicMemoryDemandMB = memory demand
    # MemoryAssignedMB = memory currently assigned
    # DynamicMemoryMaximumMB = Maximum Memory (non-zero value only if Dynamic Memory is enabled)
    return $($targetVMs|select-object Name,Version,DynamicMemoryEnabled,DynamicMemoryDemandMB,MemoryAssignedMB|sort-object -Property Name)
}

function setVmDynamicMemoryInVmm{
    param(
        $vmName,
        $setMinMb,
        $setMaxMb,
        $setStartupMb,
        $forcedRestart=$false,
        $buffer=20,
        $memoryWeight=5000,
        $vmmServer='localhost'
    )
    
    Import-Module -Name "virtualmachinemanager"
    $vm=get-scvirtualmachine -name $vmName -vmmserver $vmmServer
    $staticMemory=!$vm.DynamicMemoryEnabled # if Dynamic Memory is enabled and VM generation is 9.0 or higher, then Dynamic RAM can be configured while VM remains online
    $maximumMB=if($setMaxMb -gt $vm.MemoryAssignedMB){$setMaxMb}elseif($vm.MemoryAssignedMB -gt $vm.DynamicMemoryDemandMB){$vm.MemoryAssignedMB}elseif($vm.DynamicMemoryDemandMB -gt $vm.Memory){$vm.DynamicMemoryDemandMB}else{4096}
    $startupMb=if($startupMb){$startupMb}elseif($vm.DynamicMemoryDemandMB -lt $maximumMB/2){$vm.DynamicMemoryDemandMB}else{2048}
    $minimumMB=if($setMinMb){$setMinMb}elseif($vm.DynamicMemoryDemandMB -lt $maximumMB/2){$vm.DynamicMemoryDemandMB}else{$startupMb}

    if(!$vm -or $vm.count -gt 1){
        write-warning "VM Name $vmName cannot be queried"
        return $false
    }
    
    $vmHasFailed=$vm.Status -eq 'Failed'
    if($vmHasFailed){
        try{
            # Manually repair virtual machine in a failed state
            Repair-SCVirtualMachine $vm -Dismiss 
        }catch{
            write-warning $_
            return $false
        }
    }

    $vmIsOnline=$vm.VirtualMachineState -eq 'Running'
    if($vmIsOnline -and $forceRestart -and $staticMemory){
        Stop-SCVirtualMachine -VM $vm
        write-host "$vmName has been stopped temporarily"
    }elseif($vmIsOnline -and !$forceRestart -and $staticMemory){
        write-warning "VM Name $vmName status is 'Running' and the `$forceRestart flag has been set to `$false"
        return $false
    }elseif(!$staticMemory){
        write-host "VM name $vmName memory config is detected as dynamic"
    }
    # Hyper-V Commands - will not work in VMM
    # $startupBytes=$startupGb/1
    # $minBytes=$minGb/1
    # $maxBytes=$maxGb/1
    # Set-VMMemory $vmName -DynamicMemoryEnabled $true -MinimumBytes $minBytes -StartupBytes $startupBytes -MaximumBytes $maxBytes -Priority $priority -Buffer $buffer
    
    # VMM Command
    try{
        Set-SCVirtualMachine -VM $vm -DynamicMemoryEnabled $true `
            -MemoryMB $startupMb -DynamicMemoryMinimumMB $minimumMB -DynamicMemoryMaximumMB $maximumMB `
            -DynamicMemoryBufferPercentage $buffer -MemoryWeight $memoryWeight
        if($vmIsOnline -and $forceRestart){        
            Start-SCVirtualMachine -VM $vm
            if($vm.Status -eq 'Running'){
                write-host "$vmName has started"
            }else{
                write-host "$vmName has NOT started successfully"
                return $false
            }
        }
        write-host "VM name $vmname memory has been set to dynamic with min of $minGb GB and $maxGb GB"
        return $true
    }catch{
        write-warning $_ 
        return $false
    }

    # Set Static RAM
    # Set-VM -StaticMemory -Name $vmName -MemoryStartupBytes $($startupGb/1)
}

# setVmDynamicMemoryInVmm $vmName $setMinMb $setMaxMb $forceRestart $buffer $memoryWeight $vmmServer
function optimizeMemoryVms{
    param(
        $minGb='16GB',
        $maxGb='64GB',
        $startupGb='2GB',
        $forcedRestart=$false,
        $buffer=20,
        $memoryWeight=5000,
        $vmmServer='localhost'
    )
    $targetVMs=getUnoptimizedMemoryVms $minGb $maxGb $vmmServer
    if($targetVMs.count){
        $results=[hashtable]@{}
        foreach($vm in $targetVMs){
            $vmName=$vm.Name
            $setMaxMb=if($vm.DynamicMemoryDemandMB -gt $vm.MemoryAssignedMB){$vm.DynamicMemoryDemandMB}else{$vm.MemoryAssignedMB}
            $setStartupMb=if($startupGb){$startupGb/1MB}elseif($vm.DynamicMemoryDemandMB -lt $setMaxMb/2){$vm.DynamicMemoryDemandMB}else{2048}
            $setMinMb=if($($vm.MemoryAssignedMB/2) -lt $setStartupMb){$vm.MemoryAssignedMB/2}else{$setStartupMb}
            $result=setVmDynamicMemoryInVmm $vmName $setMinMb $setMaxMb $setStartupMb $forceRestart $buffer $memoryWeight $vmmServer
            write-host "$vmName Memory config as been set to Dynamic with Min $setMinMb MB and Max $setMaxMb MB"
            $results+=@{$vmName=$result}
        }
        return $results
    }
}

optimizeMemoryVms $minGb $maxGb $startupGb $forcedRestart $buffer $memoryWeight $vmmServer



# Set-SCVirtualMachine -Name TESTWINDOWS -DynamicMemoryEnabled $true -DynamicMemoryMinimumMB $(8*1024)

# Set-VMMemory TESTWINDOWS -StartupBytes $(8GB/1)

# PS C:\Windows\system32> Set-VMMemory $vmName -StartupBytes $($startupGb/1)
# Set-VMMemory : Failed to modify device 'Memory'.
# 'TESTWINDOWS' failed to modify device 'Memory'. (Virtual machine ID
# 0A1E6930)
# Changing memory size of running virtual machine 'TESTWINDOWS' failed. VM version must be at least 6.0.
# At line:1 char:1
# + Set-VMMemory $vmName -StartupBytes $($startupGb/1)
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#     + CategoryInfo          : NotSpecified: (:) [Set-VMMemory], VirtualizationException
#     + FullyQualifiedErrorId : OperationFailed,Microsoft.HyperV.PowerShell.Commands.SetVMMemory

# Install-WindowsFeature -Name Hyper-V-PowerShell
# Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell

PowerShell: Set DNS Records on Remote Computers

# setDnsEntries.ps1

$computernames=@(
    "$env:computername"
)
$dnsServers=@(
    "8.8.8.8",
    "4.4.2.2"
)

$results=[hashtable]@{}
foreach ($computername in $computernames){
    $psSession=new-pssession $computername
    if($psSession.State -eq 'Opened'){
        $result=invoke-command -session $pssession {
            param($dnsServers)
            try{
                $defaultInterfaceIndex=(Get-NetRoute -DestinationPrefix "0.0.0.0/0").IfIndex
                Set-DnsClientServerAddress -InterfaceIndex $defaultInterfaceIndex -ServerAddresses $dnsServers
                write-host "$env:computername`:`r`n$((Get-DnsClientServerAddress -interfaceindex $defaultInterfaceIndex|?{$_.AddressFamily -eq 'IPv4'}|out-string).trim())"
                return $true
            }catch{
                write-warning $_ 
                return $false
            }            
        } -ArgumentList (,$dnsServers)
        $results+=[hashtable]@{$computername=$result}
        remove-pssession $psSession
    }else{
        write-warning "Unable to connect to $computername"
        $results+=[hashtable]@{$computername='unableToConnect'}
    }
    pause
}

PowerShell: How To Append to Windows Environmental Paths Permanently

Often, when we install applications such as Python, its bin directory may not automatically be added to the OS environmental paths. Most of us would use the command $env:path+=”;$appendPath” to work around this issue; however, such command only works for that single user or session.

How do we make a permanent change to the System Environmental paths? Here’s a function for that.

*** WARNING: this is only for educational purposes. Please DO NOT apply this to PRODUCTION systems without knowing what you’re doing as misuse can lead to system failures! Please consult a professional. Caveat Emptor! ***

$appendPath='C:\Python38\Scripts'

function appendEnvironmentPaths($appendPath){
    $appendPath=if($appendPath -match '\\$'){$appendPath.Substring(0,$appendPath.Length-1)}else{$appendPath}
    $regEnvironmentPaths='Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment'
    $regKeyname='PATH'
    $systemPaths=(Get-ItemProperty -Path $regEnvironmentPaths -Name $regKeyname)."$regKeyname"
    $systemPathsArray=$systemPaths -split ';'
    $pathExists=.{
        $systemPathsArray|%{
            $path=if($_ -match '\\$'){$_.Substring(0,$_.Length-1)}else{$_}
            if($path -eq $appendPath){
                return $true
            }
        }
    }
    if(!$pathExists){
        $systemPaths+=";$appendPath"
        Set-Itemproperty -path $regEnvironmentPaths -Name $regKeyname -value $systemPaths
        write-host "$appendPath has been appended:`r`n$systemPaths"
$env:Path=[System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
    }else{
        write-host "$appendPath already exists in System Paths:`r`n$systemPaths"
    }
}

appendEnvironmentPaths $appendPath

PowerShell: Grant SMB Access

# grantSmbAccess.ps1

$computername='fileserver'
$shareName='TEST'
$account='domain\groupOrUsername'
$accessType='read' # also change or full

invoke-command -computername $computername -scriptblock{
    param($shareName,$account,$accessType)
    try{
        Grant-SmbShareAccess -Name $shareName -AccountName $account -AccessRight $accessType -force
        # Get-SmbShareAccess $shareName
        $object=(Get-SmbShare $shareName).Path
        # Translating SMB to NTFS equivalent: Full, Modify, ReadAndExecute
        $permission=switch($accessType.tolower()){
            'full' {'full'}
            'change' {'modify'}
            'read' {'ReadAndExecute'}
            Default {'ReadAndExecute'}
        }
        $acl=Get-Acl $object
        $grantAccess=New-Object System.Security.AccessControl.FileSystemAccessRule($account,$permission,'Allow')
        $acl.AddAccessRule($grantAccess)
        Set-Acl $object $acl
        return $true
    }catch{
        write-warning $_
        return $false
    }

} -Args $shareName,$account,$accessType

How To Use CredSSP to Move Virtual Machines In a Hyper-V Cluster

# Prep on Client
$domain='intranet.kimconnect.com'
Enable-WSManCredSSP -Role "Client" -DelegateComputer "*.$domain"

# Prep on Server
Enable-WSManCredSSP -Role "Server"

# Test a command the requires CredSSP
$adminUsername='intranet\brucelee'
$plaintextPassword='SOMEPASSWORD'
$encryptedPassword=ConvertTo-securestring $plaintextPassword -AsPlainText -Force
$adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -Args $adminUsername,$encryptedPassword

$vmName='testWindows'
$currentNode='lax-node002'
$newOwnerNode='lax-node008'

invoke-command -computername $currentNode -credential $adminCredential -scriptblock {
		param($vmName,$newOwnerNode)
		Move-ClusterVirtualMachineRole -Name $vmName -MigrationType Live -Node $newOwnerNode
	} -Args $vmName,$newOwnerNode

Installing DotNet 3.5 on Windows with Restricted Internet Access

# Set Windows Source files
# Assuming that a Windows ISO / DVD Rom have been mounted as F:\ volume
$windowsSources='F:\sources\sxs'
dism /online /enable-feature /featurename:NetFX3 /All /Source:$windowsSources /LimitAccess

# Sample output
# [TESTWINDOWS]: PS f:\> dism /online /enable-feature /featurename:NetFX3 /all /Source:$windowsSources /LimitAccess

# Deployment Image Servicing and Management tool
# Version: 10.0.20348.681
# Image Version: 10.0.20348.707
# Enabling feature(s)
# [===========================61.0%===                       ]
# [==========================100.0%==========================]
# The operation completed successfully.

Disable and Enable Trace Logging for Dynamics CRM

# Set common variables
$serverTracingRegistry='Registry::HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\MSCRM'

# Enable CRM Tracing
Add-PSSnapin Microsoft.Crm.PowerShell
$setting = Get-CrmSetting TraceSettings
$setting.Enabled = $True
$setting.Directory = "D:\crmTraceLogs"
Set-CrmSetting $setting
$traceRefreshCurrentValue=(Get-ItemProperty -path $serverTracingRegistry).'TraceRefresh'
Set-Itemproperty -path $serverTracingRegistry -Name 'TraceRefresh' -value $($traceRefreshCurrentValue++)

# Disable Tracing
Add-PSSnapin Microsoft.Crm.PowerShell
$setting = Get-CrmSetting TraceSettings
$setting.Enabled = $False
Set-CrmSetting $setting
Set-Itemproperty -path $serverTracingRegistry -Name 'TraceRefresh' -value 0

 
# Check current settings
Get-CrmSetting TraceSettings
Get-ItemProperty -path $serverTracingRegistry -Name 'TraceRefresh'

PowerShell: Find Locking PID of a File

$filePath="C:\Program Files\Google\Chrome\Application\chrome.exe"

function findPidOfFile($filepath){    
    try{
        if (!(Get-Command handle.exe -ErrorAction SilentlyContinue)) {
            if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))    
            }
            choco install sysinternals -y --ignore-checksums
        }
        $handles=handle.exe
        $matchedLines=$handles|?{$_ -like "*$filepath*"}
        $lockingPids=@();
        $lastKnownPid="";
        if($null -ne $matchedLines){
            foreach ($line in $matchedLines) {
            $lastKnownPid=.{
                [void]($line -match "pid:\s(.*)\s");
                if ($matches[1]){return $matches[1]}
                }
            if ($line -like "*$filepath*") {
                return $lastKnownPid;
                }
            }
        }else{
            write-host "$filepath does NOT currently have a locking pid"
        }
    }catch{
        write-warning $_
    }
}

findPidOfFile $filePath

PowerShell: Automatically Log Off Idling Remote Desktop Sessions

$computernames=@(
  'LAX-RDSNODE01'
)
$idleDaysThreshold=3
$forcedLogoff=$true

# Obtain credentials being passed by Jenkins
$runasUser=$env:runasUser
$runasPassword=$env:runasPassword
$encryptedPassword=ConvertTo-SecureString $runasPassword -AsPlainText -Force
$runasCredentials=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $runasUser,$encryptedPassword

# Email relay parameters
$emailFrom='reports@kimconnect.com'
$emailTo='sysadmins@kimconnect.com','sysAdmins@dragoncoin.com'
$subject='Hyper-V Hosts Capacity Report'
$smtpRelayServer='relay02.dragoncoin.com'


function getSessionsInfo([string[]]$computernames=$env:computername,$runasCredentials){
  $results=@()
  function checkPorts($servers,$ports){
    function includePortQry{
        if (!(Get-Command portqry.exe -ErrorAction SilentlyContinue)){
            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 portqry -y
            if (Get-Command portqry.exe -ErrorAction SilentlyContinue){return $true}else{return $false}
        }else{
            return $true
        }
    }
    $portQryExists=includePortQry
    if(!$portQryExists){
      write-warning "Unable to proceed without portqry"
      return $false
    }
    $results=@()
    foreach ($remoteComputer in $servers){
      #$ip=[System.Net.Dns]::GetHostAddresses($remoteComputer).IPAddressToString|select -first 1
      write-host "Now scanning $remoteComputer"
      $result=@()
      foreach ($item in $ports){
          $port=$item.port
          $protocol=$item.protocol        
          $reachable=if($port -ne 135){                
                  $command={
                      param($remoteComputer,$protocol,$port)
                      $command="portqry -n $remoteComputer -p $protocol -e $port|find ': LISTENING'"
                      invoke-expression $command
                  }
                  $jobId=(Start-Job $command -Args $remoteComputer,$protocol,$port).Id
                  $startCount=0;$waitSeconds=5
                  do{
                      $jobStatus=(get-job -id $jobId).State
                      if($jobStatus -eq 'Completed'){
                          $jobResult=receive-job $jobId
                      }else{
                          if($startCount++ -eq $waitSeconds){
                              $jobStatus='Completed'
                              $null=remove-job -id $jobId -force
                              $jobResult=$false
                          }else{
                              sleep 1
                          }                        
                      }
                  }until($jobStatus -eq 'Completed')
                  [bool]($jobResult)
              }else{
                  [bool](portqry -n $remoteComputer -p $protocol -e $port|find 'Total endpoints found: ')
              }       
          write-host "$port/$protocol : $reachable"
          $result+=[PSCustomObject]@{
              computername=$remoteComputer
              port=$port
              protocol=$protocol
              reachable=$reachable
          }
      }
      #$resultString=$results.GetEnumerator()|sort-object {[int]$_.Name}|out-string
      $results+=$result
      $resultString=$result|sort-object -property port|out-string
      write-host $resultString
    }
    return $results
  }
  function getDisconnectedSessionInfo($line,$computer){
           # convert multiple spaces into single space and split pieces into array
          $thisLine = $line.Trim() -Replace '\s+',' ' -Split '\s'
          $properties = @{
              UserName = $thisLine[0]
              ComputerName = $computer
              }
          $properties.SessionName = $null
          $properties.Id = $thisLine[1]
          $properties.State = $thisLine[2]                        
          $properties.IdleMinutes=.{[string]$x=$thisLine[3]
                          switch -regex ($x){
                              '\.' {0;break}                   
                              '\+' {$dayMinutes=.{[void]($x -match '^(\d+)\+');[int]($matches[1])*1440}
                                      $hourMinutes=.{[void]($x -match '\+(.*)$');([TimeSpan]::Parse($matches[1])).totalMinutes}
                                      $dayMinutes+$hourMinutes
                                      break;
                                   }               
                              '\:' {try{
                                      ([TimeSpan]::Parse($x)).totalMinutes
                                      }catch{
                                          "Invalid value: $x"
                                          }
                                      break
                                      }
                              default {$x}
                          }                                  
                      }
          $properties.LogonTime = $thisLine[4..6] -join ' '
          $result=New-Object -TypeName PSCustomObject -Property $properties
          return $result
  }

  function getConnectedSessionInfo($line,$computer){
          $thisLine = $line.Trim() -Replace '\s+',' ' -Split '\s'
          $properties = @{
              UserName = $thisLine[0]
              ComputerName = $computer
              }
          $properties.SessionName = $thisLine[1]
          $properties.Id = $thisLine[2]
          $properties.State = $thisLine[3]
          $properties.IdleMinutes=.{$x=$thisLine[4]
                          switch -regex ($x){
                              '\.' {0;break}                   
                              '\+' {$dayMinutes=.{[void]($x -match '^(\d+)\+');[int]($matches[1])*1440}
                                      $hourMinutes=.{[void]($x -match '\+(.*)$');([TimeSpan]::Parse($matches[1])).totalMinutes}
                                      $dayMinutes+$hourMinutes
                                      break;
                                   }               
                              '\:' {try{
                                      ([TimeSpan]::Parse($x)).totalMinutes
                                      }catch{
                                          "Invalid value: $x"
                                          }
                                      break
                                      }
                              default {$x}
                          }                                 
                      }
          $properties.LogonTime = $thisLine[5..($thisLine.GetUpperBound(0))] -join ' '
          $result=New-Object -TypeName PSCustomObject -Property $properties
          return $result
  }

  foreach ($computer in $computernames){
    if((checkPorts $computer @{protocol='tcp';port=135}).reachable){
      try {
        # Perusing legacy commandlets as there are no PowerShell equivalents at this time
        $sessions=if($runasCredentials){
            invoke-command -computername $computer -credential $runasCredentials {quser /server:$computer 2>&1 | Select-Object -Skip 1}
        }else{
            quser /server:$computer 2>&1 | Select-Object -Skip 1
        }
        ForEach ($session in $sessions) {               
            $result=$null
            $thatLine = $session.Trim() -Replace '\s+',' ' -Split '\s'
            if ($thatLine[2] -eq 'Disc'){
                $result=getDisconnectedSessionInfo $thatLine $computer
            }elseif(!$onlyDisconnected){
                $result=getConnectedSessionInfo $thatLine $computer
            }
            if($result){$results+=$result}
        }
      }catch{
          $results+=New-Object -TypeName PSCustomObject -Property @{
              ComputerName=$computer
              Error=$_.Exception.Message
          } | Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleMinutes,LogonTime,Error|sort -Property UserName
      }
    }else{
      $results+=New-Object -TypeName PSCustomObject -Property @{
        ComputerName=$computer
        Error=$_.Exception.Message
      } | Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleMinutes,LogonTime,Error|sort -Property UserName
    }
  }
  return $results
}

#getSessionsInfo $computernames $runasCredentials

function logOffRdpSession{
  param(
    $serverName=$env:computername,
    $username,
    $idleMinutes,
    $runasCredentials
  )
  $username=if($username -match '\\'){[regex]::match($username,'\\(.*)$').captures.groups.value[1]
    }else{
      $username.tostring()
    }
  $sessions=if(!$runasCredentials){
      qwinsta /server:$serverName
  }else{
      invoke-command -computername $servername -credential $runasCredentials -scriptblock {qwinsta /server:$env:computername}
  }
  $sessionId=.{
      $sessionMatch=$sessions|?{$_ -match $username}
      if($sessionMatch){
          $array=$sessionMatch -replace '(^\s+|\s+$)','' -replace '\s+',' ' -split ' '
          return $array|?{$_.tostring() -match '\d+'}
      }else{
          return $null  
      }
  }
  if($sessionId){
    if(!$runasCredentials){
        $sessionId|%{rwinsta $_ /SERVER:$serverName}
        $sessions=qwinsta /server:$serverName
        $newSessionId=.{
            $sessionMatch=$sessions|?{$_ -match $username}
            if($sessionMatch){
                $array=$sessionMatch -replace '(^\s+|\s+$)','' -replace '\s+',' ' -split ' '
                return $array[2]
            }else{
                return $null  
            }
        }
    }else{
        invoke-command -computername $servername -credential $runasCredentials -scriptblock{
            param($sessionId,$username)
            $sessionId|%{rwinsta $_ /SERVER:$env:computername}
            $sessions=qwinsta /server:$env:computername
            $newSessionId=.{
                $sessionMatch=$sessions|?{$_ -match $username}
                if($sessionMatch){
                    $array=$sessionMatch -replace '(^\s+|\s+$)','' -replace '\s+',' ' -split ' '
                    return $array[2]
                }else{
                    return $null  
                }
            }
        } -Args $sessionId,$username
    }
    if(!$newSessionId){
      write-host "$username RDP session ID $sessionId on $serverName has been forcefully disconnected due to its idling $idleMinutes minutes."
      return $true
    }else{
      write-warning "$username RDP session ID $sessionId still exists on $serverName"
      return $false
    }      
  }else{
    write-host "$username doesn't have an RDP session on $serverName"
    return $true
  }
}

# logOffRdpSession $computerName $username $runasCredentials

$allSessions=getSessionsInfo $computernames $runasCredentials
$targetSessions=$allSessions|?{[Int]$_.IdleMinutes -ge $idleDaysThreshold*1440}
#$logoffSessions=@()
if($forcedLogoff){
  foreach($session in $targetSessions){
    $result=logOffRdpSession $session.ComputerName $session.UserName $session.IdleMinutes $runasCredentials
    if($result){
      $targetSessions=$targetSessions|?{$_ -ne $session}
      #$logoffSessions+=$sessions
    }
  }
}
$sessionsToEmail=$targetSessions # future development: add a routine to check whether current report is identical to previous

if($null -ne $sessionsToEmail){
  $css="
  <style>
  .h1 {
      font-size: 18px;
      height: 40px;
      padding-top: 80px;
      margin: auto;
      text-align: center;
  }
  .h5 {
      font-size: 22px;
      text-align: center;
  }
  .th {text-align: center;}
  .table {
      padding:7px;
      border:#4e95f4 1px solid;
      background-color: white;
      margin-left: auto;
      margin-right: auto;
      width: 100%
      }
  .colgroup {}
  .th { background: #0046c3; color: #fff; padding: 5px 10px; }
  .td { font-size: 11px; padding: 5px 20px; color: #000;
        width: 1px;
        white-space: pre;
      }
  .tr { background: #b8d1f3;}
  .tr:nth-child(even) {
      background: #dae5f4;
      width: 1%;
      white-space: nowrap
  }
  .tr:nth-child(odd) {
      background: #b8d1f3;
      width: 1%;
      white-space: nowrap
  }
  pre code {
    background-color: #eee;
    border: 1px solid #999;
    display: block;
    padding: 20px;
  }
  </style>
  "
  $howToLogOffSessionCode="<pre><code>function logOffRdpSession"+${function:logOffRdpSession}+"}`r`n`r`nlogOffRdpSession SERVERNAME USERNAME</code></pre>"
  $sessionsReformatted=$sessionsToEmail|select ComputerName,UserName,@{n='sessionId';e={$_.id}},IdleMinutes,State,LogonTime
  $currentReport=$sessionsReformatted|ConvertTo-Html -Fragment|Out-String
  $currentReportHtml=$currentReport -replace '\<(?<item>\w+)\>','<${item} class=''${item}''>'
  $howToLogOffSessionCode=$howToLogOffSessionCode -replace '\<(?<item>\w+)\>','<${item} class=''${item}''>'
  $emailContent='<html><head>'+$css+"</head><body><h5 class='h5'>$subject</h5>"+$currentReportHtml+"<br><br><h1 class='h1'>Function to forcefully log off a session</h1>"+$howToLogOffSessionCode+'</body></html>'
  Send-MailMessage -From $emailFrom `
  -To $emailTo `
  -Subject $subject `
  -Body $emailContent `
  -BodyAsHtml `
  -SmtpServer $smtpRelayServer 
}else{
  write-host "No idle sessions to notify Admins."
}

PowerShell: How to Reset Windows Update Service

# resetWindowsUpdateService
# This is a legacy method of reseting Windows Update
# Since most enterprises are having antiviruses nowadays, I've included an example of how to disable Palo Alto XDR Traps are a required to reset WuApp

$trapsAdminPassword='PASSWORDHERE'
$trapsBin='C:\Program Files\Palo Alto Networks\Traps'

function resetWindowsUpdateService{
	try{
		net stop wuauserv
		net stop cryptSvc
		net stop bits
		net stop msiserver
		mkdir C:\emptyDirectory
		md -Force C:\emptyDirectory
		Remove-Item "C:\emptyDirectory\*" -force -recurse -ErrorAction Continue
		robocopy C:\emptyDirectory C:\Windows\SoftwareDistribution /mir /R:0 /W:0 /NP
		robocopy C:\emptyDirectory C:\Windows\System32\catroot2 /mir /R:0 /W:0 /NP
		net start wuauserv
		net start cryptSvc
		net start bits
		net start msiserver
		return $true
	}catch{
		write-warning $_
	}
}

function stopXdr{
	param(
		$trapsAdminPassword,
		$trapsBin='C:\Program Files\Palo Alto Networks\Traps'
	)
	echo $trapsAdminPassword | & "$trapsBin\cytool.exe" runtime stop
}

function startXdr{
	param(
		$trapsAdminPassword,
		$trapsBin='C:\Program Files\Palo Alto Networks\Traps'
	)
	echo $trapsAdminPassword | & "$trapsBin\cytool.exe" runtime start
}

stopXdr $trapsAdminPassword
resetWindowsUpdateService
startXdr $trapsAdminPassword

PowerShell: How To Stop and Start Palo Alto Cortex XDR Traps

$trapsAdminPassword='PASSWORDHERE',
$trapsBin='C:\Program Files\Palo Alto Networks\Traps'

function stopXdr{
	param(
		$trapsAdminPassword,
		$trapsBin='C:\Program Files\Palo Alto Networks\Traps'
	)
	echo $trapsAdminPassword | & "$trapsBin\cytool.exe" runtime stop
}

function startXdr{
	param(
		$trapsAdminPassword,
		$trapsBin='C:\Program Files\Palo Alto Networks\Traps'
	)
	echo $trapsAdminPassword | & "$trapsBin\cytool.exe" runtime start
}

# stopXdr $trapsAdminPassword
# startXdr $trapsAdminPassword

PowerShell: Maintaining Processes on Remote Servers

# maintainProcess.ps1
# version 0.0.1

$computernames=@(
    'server1',
    'server2'
)
$processName='cmd'
$processPath='C:\WINDOWS\system32\cmd.exe'
$minutesToDefineCrashed=1 # this marker is only valid if the process is marked as unresponsive by the system
$runMinutes=1
$credentials=$null
$maxMinutesPerJob=10
$verbose=$true

function maintainProcess{
    param(
        $targetMachines=$env:computername,
        $processname='cmd',
        $processPath='C:\Windows\system32\cmd.exe',
        $minutesToDefineCrashed=1,
        $runMinutes=2,
        $credentials
        )
    $actionOnCrash={param($processName,$processPath) 
        stop-process -name $processName -force -ea Ignore
        if($processPath){$null=start-process -FilePath $processPath}else{$null=start-process -Name $processName}
    }    
    
    function restartProcess($actionOnCrash,$processname,$processPath,$psSession){
        invoke-command -session $psSession -scriptblock $actionOnCrash -Arg $processname,$processPath
        $processIsRunning=$null -ne (invoke-command -session $psSession -scriptblock {param($processname)get-process -name $processName -EA SilentlyContinue} -Args $processname)
        if($processIsRunning){
            write-host "$processName has successfully restarted on $targetMachine"
            return $true
        }else{
            write-host "$processName has NOT successfully restarted on $targetMachine"
            return $false
        }
    }
    
    $results=[hashtable]@{}
    foreach($targetMachine in $targetMachines){
        $overallClock=[System.Diagnostics.Stopwatch]::StartNew()
        $psSession=if($credentials){
            New-PSSession $targetMachine -Credential $credentials
        }else{
            New-PSSession $targetMachine
            }     
        if($psSession.State -eq 'Opened'){      
            $previousCpuConsumption=0
            write-host "Checking CPU consumption of $processName on $targetMachine"
            do{
                $iterationClock=[System.Diagnostics.Stopwatch]::StartNew()          
                $process=Invoke-Command -Session $psSession {param($processname) Get-Process $processname -EA SilentlyContinue} -Args $processName
                if($null -eq $process){
                    write-warning "$processname is NOT running on $targetMachine"
                    $result=restartProcess $actionOnCrash $processname $processPath $psSession                    
                }else{
                    $responding=$process.Responding
                    $currentCpuConsumption=$process.CPU
                    write-host $currentCpuConsumption
                    $cpuConsumptionChanged=$currentCpuConsumption -ne $previousCpuConsumption
                    $noActivities=$iterationClock.elapsed.totalminutes -ge $minutesToDefineCrashed
                    if($cpuConsumptionChanged){
                        $null=$iterationClock.reset
                        $previousCpuConsumption=$currentCpuConsumption
                        $result=$true
                    }elseif(!$responding -and $noActivities){
                        write-warning "$processName has CRASHED on $targetMachine as defined its RESPONDING flag equal False and there are no activities."
                        $result=restartProcess $actionOnCrash $processname $processPath $psSession
                    }
                    sleep -Seconds 10
                }
            } until ($overallClock.elapsed.totalminutes -ge $runMinutes)   
            Remove-PSSession $psSession
            $minutesElapsed=[math]::round($overallClock.Elapsed.TotalMinutes,2)
            write-host "Runtime of $minutesElapsed minutes has elapsed for $targetMachine"
            $results+=[hashtable]@{$targetMachine=$result}
        }else{
            write-warning "Unable to open a WinRM session to $targetMachine.`r`nPlease monitor it's progress manually."
            $results+=[hashtable]@{$targetMachine=$null}
        }
    }
    return $results
}

# maintainProcess $targetMachines $processname $processPath $minutesToDefineCrashed $runMinutes $credential
function maintainProcessParallel{
    param(
        $computerNames=$env:computername,
        $processname='cmd',
        $processPath='C:\Windows\system32\cmd.exe',
        $minutesToDefineCrashed=1, # this marker is only valid if the process is marked as unresponsive by the system
        $runMinutes=2,
        $credentials,
        $maxMinutesPerJob=10,
        $verbose=$true
    )

    $timer=[System.Diagnostics.Stopwatch]::StartNew()
    $jobResults=@()
    $lineBreak=60
    $dotCount=0
    $minute=0
    $processorsCount=(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
    $cpuLoad=(Get-WmiObject win32_processor|Measure-Object -property LoadPercentage -Average).Average
    $maxSimultaneousJobs=if($cpuLoad -gt 90){$processorsCount}else{($processorsCount*2)-1} # dynamically limiting concurrent jobs basing on available CPU cores
    write-host "CPU load detected as: $cpuLoad`%`r`nSetting concurrent jobs max count to be $maxSimultaneousJobs"
    $jobtimer = @{}
    foreach($computerName in $computerNames){
        $thisIterationCompleted=$false
        do {
            $jobsCount=(Get-Job -State 'Running').Count
            if ($jobsCount -lt $maxSimultaneousJobs){            
                if($verbose){write-host "Initiating job for $computerName"}
                $job=Start-Job -name $computerName -ScriptBlock {
                    param($maintainProcess,$computername,$processname,$processPath,$minutesToDefineCrashed,$runMinutes,$credentials)
                    [scriptblock]::create($maintainProcess).invoke($computername,$processname,$processPath,$minutesToDefineCrashed,$runMinutes,$credential)
                } -Args ${function:maintainProcess},$computername,$processname,$processPath,$minutesToDefineCrashed,$runMinutes,$credentials
                $jobtimer[$job.Id]=[System.Diagnostics.Stopwatch]::startnew()
                $thisIterationCompleted=$true
            }else{
                if($verbose){
                    if($dotCount++ -lt $lineBreak){
                        write-host '.' -NoNewline
                    }else{
                        $minute++
                        write-host "`r`n$minute`t:" -ForegroundColor Yellow -NoNewline
                        $dotCount=0
                        }
                }
                sleep -seconds 1
            }
            $expiredJobs=$jobtimer.GetEnumerator()|?{$_.value.elapsed.totalminutes -ge $maxMinutesPerJob}
            if($expiredJobs){
                $expiredJobs.Name|%{stop-job -name $_ -EA Ignore}
                $expiredJobs.Name|%{$jobTimer.Remove($_)}
            }            
        }until ($thisIterationCompleted)
    }
    $totalJobsCount=(get-job).count
    $processedCount=0
    while($processedCount -lt $totalJobsCount){
        $completedJobs=get-job|?{$_.State -eq 'Completed'}
        $stoppedJobs=get-job|?{$_.State -eq 'Stopped'}
        $expiredJobs=$jobtimer.GetEnumerator()|?{$_.value.elapsed.totalminutes -ge $maxMinutesPerJob}
        if($expiredJobs){
            $expiredJobs.Name|%{stop-job -name $_ -EA Ignore}
            $expiredJobs.Name|%{$jobTimer.Remove($_)}
        }
        if($completedJobs){
            foreach ($job in $completedJobs){
                $computer=$job.Name
                if($verbose){
                    write-host "`r`n===================================================`r`n$computer job COMPLETED with these messages:`r`n===================================================`r`n"
                }
                $jobResult=receive-job -id $job.id
                $jobResults+=,$jobResult
                remove-job -id $job.id -force
                $processedCount++
            }
        }
        if($stoppedJobs){
            foreach ($job in $stoppedJobs){
                $computer=$job.Name
                if($verbose){
                    write-host "`r`n===================================================`r`n$computer job STOPPED with these messages:`r`n===================================================`r`n" -ForegroundColor Red
                }
                $jobResult=receive-job -id $job.id
                # $jobResults+=,$jobResult
                $timeZoneName=[System.TimeZoneInfo]::Local.StandardName
                $abbreviatedZoneName=if($timeZoneName -match ' '){[regex]::replace($timeZoneName,'([A-Z])\w+\s*', '$1')}else{$timeZoneName}
                $timeStampFormat="yyyy-MM-dd HH:mm:ss $abbreviatedZoneName"
                $timeStamp=[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([datetime]::UtcNow,$timeZoneName).ToString($timeStampFormat)
                $jobResult=[pscustomobject]@{
                    timeStamp=$timeStamp
                    computerName=$computer
                    stoppedServices='serverTimeout'
                    fixedAutomatically=$False
                    }
                $jobResults+=,$jobResult
                remove-job -id $job.id -force
                $processedCount++
            }
        }

        # Safeguard against stuck jobs
        if($timer.elapsed.totalminutes -ge $maxMinutesPerJob){
            get-job|Remove-Job -Force
            write-warning "There were some errors in this iteration. Shell was aborted to mitigate potential persistency issues."
            exit
        }
    }    
    $minutesElapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
    $timer.stop()
    write-host "$($computerNames.count) computers were processed in $minutesElapsed minutes."
    return $jobResults #|select -property * -excludeproperty RunspaceId
}

maintainProcessParallel $computerNames `
    $processname `
    $processPath `
    $minutesToDefineCrashed `
    $runMinutes `
    $credentials `
    $maxMinutesPerJob `
    $verbose

PowerShell: Find Duplicate Mac Addresses of Guest VMs in Virtual Machine Manager

Here’s a quick snippet to check whether you have any duplicate mac addresses on existing virtual machines in the cluster:

import-module virtualmachinemanager
$macAddresses=get-vm | Get-VirtualNetworkAdapter | select name,EthernetAddress

$a=$macAddresses.EthernetAddress

$b=$a | select –unique

Compare-object –referenceobject $b –differenceobject $a

PowerShell: Running Commands on Remote Computers

# runCommandsOnRemoteComputers.ps1

# User defined variables
$computernames=@(
'SERVER001',
'SERVER002'
)
$expectedExecutable='racadm.exe'
$expectedInstallPath='C:\program files\Dell\SysMgt\iDRACTools\racadm'

# Execution
foreach($computername in $computernames){
	invoke-command -computername $computername -scriptblock{
	param($expectedInstallPath,$expectedExecutable)
		$commandAvailable=try{get-command $expectedExecutable -EA Ignore}catch{$false}
		$proceed=if(!$commandAvailable){
			$environmentalPathExists=$env:path -like "*$expectedInstallPath*"
			if(!$environmentalPathExists){
				$env:path+=";$expectedInstallPath"			
			}
			$null=RefreshEnv
			$commandNowAvailable=try{get-command $expectedExecutable -EA Ignore}catch{$false}
			if($commandNowAvailable){$true}else{$false}
		}
		if($proceed){
			write-host "Configuring iDrac on $env:computername"
			racadm set iDRAC.NIC.DNSRacName ($env:computername).tolower()
			racadm set iDRAC.NIC.DNSRegister 0
			racadm set iDRAC.IPv4.DHCPEnable 1
			racadm set iDRAC.IPv4.DNSFromDHCP 1
			racadm set iDRAC.NIC.DNSDomainFromDHCP 1
			racadm set iDRAC.NIC.VLanEnable 0
		}
	} -Args $expectedInstallPath,$expectedExecutable
}

PowerShell: Installing a Program from Its Zip Archive

# installProgramFromExeZipArchive.ps1

# User inputs
$computernames=@(
	'SERVER001',
	'SERVER002'
)
$fileURL="https://dl.dell.com/FOLDER08543783M/1/DellEMC-iDRACTools-Web-WINX64-10.3.0.0-4945.exe"
$expectedExecutable='racadm.exe'
$expectedInstallPath='C:\Program Files\Dell\SysMgt\iDRACTools\racadm'
$stageFolder='C:\Temp\'

function installProgramFromExeZipArchive{
	param(
		$computernames=$env:computername,
		$fileURL="https://dl.dell.com/FOLDER07549599M/1/DellEMC-iDRACTools-Web-WINX64-10.2.0.0-4583_A00.exe",
		$expectedExecutable='racadm.exe',
		$expectedInstallPath='C:\program files\Dell\SysMgt\iDRACTools\racadm',
		$stageFolder='C:\Temp\'
	)
	$results=[hashtable]@{}
	foreach($computerName in $computernames){
		$alreadyInstalled=invoke-command -computername $computername -scriptblock{param($expectedExecutable);try{get-command $expectedExecutable -EA Ignore}catch{$false}} -Args $expectedExecutable
		if($alreadyInstalled){
			write-host "$computername has already installed $expectedExecutable"
			$results+=[hashtable]@{$computername=$true}
		}else{
			# Autogen variables
			$fileName=[regex]::match($fileURL,'[^/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))').value
			$translatedVolume=[regex]::match($stageFolder,'^(\w)\:').captures.groups[1].value+'$'
			$translatedFoldername=[regex]::match($stageFolder,'\:(.*)$').captures.groups[1].value
			$remoteSmbPath=join-path $('\\'+$computerName+"\$translatedVolume") $translatedFoldername
			try{
				# Download the file directly onto target server's staging folder
				Import-Module BitsTransfer
				if(!(test-path $remoteSmbPath)){$null=mkdir $remoteSmbPath}
				# Start-BitsTransfer -Source $fileURL -Destination $output
				[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
				Start-BitsTransfer -Source $fileURL -Destination $remoteSmbPath
				$psSession=new-pssession $computerName
				if($psSession.State -eq 'Opened'){
					$result=invoke-command -Session $psSession -ScriptBlock{
						param($stageFolder,$filename,$expectedExecutable,$expectedInstallPath)
						# write-host "$env:computername : $stageFolder,$filename,$expectedExecutable,$expectedInstallPath"
						# pause					
						$commandAvailable=try{get-command $expectedExecutable -EA Ignore}catch{$false}
						$toInstall=if(!$commandAvailable){
							if(!($env:path -like "*$expectedInstallPath*")){$env:path+=";$expectedInstallPath"}						
							$null=RefreshEnv
							$commandNowAvailable=try{get-command $expectedExecutable -EA Ignore}catch{$false}
							if($commandNowAvailable){$false}else{$true}
						}else{
							write-host "Command $expectedExecutable is available on $env:computername"
							$true
						}
						if ($toInstall){
							$exeExists=try{ls $expectedInstallPath -EA Ignore|?{$_.Name -like $expectedExecutable}}catch{$false}
							if($exeExists){
								$env:path+=";$expectedInstallPath"
								$null=RefreshEnv
								$commandNowAvailable=try{get-command $expectedExecutable -EA Ignore}catch{$false}
								if($commandNowAvailable){return $true}else{return $false}
							}else{
								$expectedFileLocation=join-path $stageFolder $filename
								$zipFilename=$([System.IO.Path]::GetFileNameWithoutExtension($expectedFileLocation))+'.zip'
								$newZipFile=join-path $stageFolder $zipFilename
								#[System.IO.Path]::GetExtension($expectedFileLocation)
								Rename-Item -Path $expectedFileLocation -NewName $newZipFile -EA Ignore
								# $null=Expand-Archive -Path $newZipFile -DestinationPath $stageFolder
								# alternative method: backward compatible to older systems
								Add-Type -AssemblyName System.IO.Compression.FileSystem
								$null=[System.IO.Compression.ZipFile]::ExtractToDirectory($newZipFile, $stageFolder)
								$msiFile=(ls $stageFolder|?{$_.FullName -match '\.msi$'}|sort -property LastWriteTime|select -first 1).FullName
								Start-Process $msiFile -ArgumentList "/quiet" -wait
								# this command doesn't work: msiexec /i $msiFile /quiet /qn /norestart
								$environmentalPathExists=$env:path -like "*$expectedInstallPath*"
								if(!$environmentalPathExists){
									$env:path+=";C:\Program Files\Dell\SysMgt\iDRACTools\racadm"
									$null=RefreshEnv
								}
								$commandNowAvailable=try{get-command $expectedExecutable -EA Ignore}catch{$false}
								if($commandNowAvailable){return $true}else{return $false}
							}
						}					
					} -Args $stageFolder,$fileName,$expectedExecutable,$expectedInstallPath
					Remove-PSSession $psSession
					write-host "$computername result: $result"
					$results+=[hashtable]@{$computername=$result}
				}else{
					write-warning "Unable to connect to $computername via WinRM"
					$results+=[hashtable]@{$computername=$null}
				}
			}catch{
				write-warning $_
				$results+=[hashtable]@{$computername=$null}
			}
		}
	}
	return $results
}
installProgramFromExeZipArchive

How To Remove A Program on Windows Using PowerShell

# removeAppwizProgram.ps1
# Version 0.02

$computernames=@(
    'SERVER0001',
'SERVER0002'
)
$appName='Dell EMC OpenManage Systems Management Software (64-Bit)'
function removeAppwizProgram($computernames=$env:computername,$appName='Firefox'){
    $results=[hashtable]@{}
    foreach($computer in $computernames){
        $session=new-pssession $computer
        if($session.State -eq 'Opened'){
            $result=invoke-command -session $session -scriptblock{
                param($appName)
                write-host "Checking $env:computername..."
                try{
                    # Method 1: try using the Uninstall method of the application packager
                    $app=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                    if($app.Name -eq $appName){
                        write-host "Uninstalling $app"
                        # pause
                        $null=$app.Uninstall()
                        $appStillExists=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                        if($appStillExists){
                            write-host "'$appName' still exists"
                            # Method 2: Using Registry
                            $uninstallStringRegPaths='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
                            $uninstallStrings=Get-ChildItem -Path $uninstallStringRegPaths
                            $uninstallString=($uninstallStrings|Get-ItemProperty|Where-Object {$_.DisplayName -match $appName}).UninstallString
                            if($uninstallString.count -eq 1){
                                $appCode=[regex]::match($uninstallString,'\{(.*)\}').Value
                                $uninstallCommand="& msiexec.exe /x $appCode /quiet /norestart"
                                write-host "Invoking uninstall Command: $uninstallCommand"
                                Invoke-Expression $uninstallCommand
                                $appStillExists=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                                if($appStillExists){
                                    write-warning "Uninstall has been unsuccessful at removing $appName"
                                    return $false
                                }else{
                                    return $true
                                }
                            }else{
                                write-warning "Please check this/these uninstall string(s):`r`n$uninstallString"
                                return $false
                            }
                        }else{
                            write-host "'$appName' has been removed"
                            return $true
                        }
                    }else{
                        write-host "No matches for $appName"
                        return $true
                    }                   
                }catch{
                    write-warning $_
                    return $false
                }
            } -Args $appName
            $results+=@{$computer=$result}
            remove-pssession $session
        }else{
            write-warning "Unable to connect to $computer"
            $results+=@{$computer=$null}
        }        
    }
    return $results
}

removeAppwizProgram $computernames $appName
Other project: DragonCoin.com