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

Changing User Password Using Command Lines or PowerShell

# Command-line method
net user usernamehere newpasswordhere

# Powershell Method 1 - via ActiveDirectory module
$username="USERNAMEHERE"
$newPassword="NEWPASSWORDHERE"
$encryptedPass= ConvertTo-SecureString $newPassword -AsPlainText -Force 
Set-ADAccountPassword -Identity $username -NewPassword $encryptedPass -Reset

# Powershell method 2 - via ASDI
$userid = [ADSI]"LDAP://CN=USERNAME,OU=Users,DC=DOMAIN,DC=Local"
$userid.psbase.invoke("SetPassword",'NEWPASSWORDHERE')
$userid.psbase.CommitChanges()

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
}

Windows Event ID 2017: Unable to collect NUMA physical memory utilization data

Issue:
Log Name: Application
Source: Microsoft-Windows-PerfOS
Date: 9/12/2018 7:47:38 AM
Event ID: 2017
Task Category: None
Level: Warning
Keywords: Classic
User: N/A
Computer: TESTWINDOWS
Description:
Unable to collect NUMA physical memory utilization data. The first four bytes (DWORD) of the Data section contains the status code.
Event Xml:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-PerfOS" Guid="{F82FB576-E941-4956-A2C7-A0CF83F6450A}" EventSourceName="PerfOS" />
<EventID Qualifiers="32768">2017</EventID>
<Version>0</Version>
<Level>3</Level>
<Task>0</Task>
<Opcode>0</Opcode>
<Keywords>0x80000000000000</Keywords>
<TimeCreated SystemTime="2018-09-12T14:47:38.788262000Z" />
<EventRecordID>118276</EventRecordID>
<Correlation />
<Execution ProcessID="0" ThreadID="0" />
<Channel>Application</Channel>
<Computer>TESTWINDOWS</Computer>
<Security />
</System>
<EventData>
<Binary>05000080</Binary>
</EventData>
</Event>
Resolution:
[TESTWINDOWS]: PS C:\windows\system32> lodctr /R

Info: Successfully rebuilt performance counter setting from system backup store

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

Issue: A-Host Records Disappear in an Active Directory Integrated DNS Environment with Windows DHCP Server

Issue:

Certain virtual machines would loose their a-host records after a period of time. That would lead to such machines being inaccessible by users by computer names.

Investigation:

DHCP Server Settings:

DNS Server Settings:

DNS Zone Settings

A-host record settings

Analysis:

According to the screenshots above, we can derive at this generalization:

 – No-refresh interval = 7 days
 – Refresh interval = 7 days
 – Scavenge stale records = enabled at [domain name] zone
 – Scavenge period = not enabled at server level

Hence, the DNS server does not have a default policy to delete stale records. However, the domain zone level settings have this effect (by having a check mark next to ‘scavenge stale resource records’). Furthermore, since the record is set as ‘dynamic’, it would automatically delete itself after a refresh interval + no refresh interval or 7 + 7 = 14 days have expired.

More importantly, the DHCP server option to ‘discard A and PTR records when lease is deleted’ would also trigger an automatic deletion. Hence, any machine that has a DHCP lease would be at risk of its associated DNS records being purged if its lease doesn’t get renewed. Therefore, if a machine leases an IP, then subsequently sets that IP as static in its TCP/IP settings, would run the risk of its a-host record being pruned due to it no longer sending out DHCP renew packets to the DHCP server.

There may be other factors, such as network layer 1-3 issues preventing packets from leaving the clients or reaching the servers. In such scenarios, that would have a similar effect of the client not renewing its lease toward the DHCP servers and performing refreshes toward DNS servers. Thus, such client machines and their associated DNS records would also be cleared automatically.

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.

Windows: How To Expand Disk Volumes That Are Not Adjacent the Intended Volume

Problem:

Here’s a scenario when a virtualized disk has been expanded in Hyper-V, Vmware, AWS, Azure, Google Cloud, etc. The intended disk to be expanded is C:\, but E:\ is blocking C:\ from being expanded using the Windows diskmgmt.msc. What to do?

Resolution:

Option 1: Windows 7/10/11 (Client OS)

Install AOMEI Partition Assistant Standard

Option 2: Windows Servers

Pay the licensing fee to be able to use the partitioning wizard on a Windows Server OS

Option 3: Change vDisk bus type

  • Clone the existing Guest VM as a full backup
  • While having target Guest VM powered off, edit its VHD file > change ddb.adapterType = “ide” to ddb.adapterType = “lsilogic”
  • Remove the drive from the Guest VM with the “DO NOT remove from virtual machine and delete files from disk” option
  • Add a new hard disk > choose the “Use an existing virtual disk” option > select the targeted hard disk

Option 4: Windows Server 2012 / 2016 / 2019 / 2022 as a Guest VM

Caveat: this have proven to work on volumes with files that are NOT constantly changing (e.g. NOT databases, OS, real-time applications with transient items)

  • Add a new volume
  • Take a VSS Snapshot of the volume to be moved
$targetVolume="E:\"
$vssAccessLink="C:\shadowcopy"

function createVssSnapshot{
[cmdletbinding()]
param(
[string]$targetVolume="C:\",
$vssAccessLink="C:\shadowcopy"
)
# Sanitation
if (!($targetVolume -like "*\")){$targetVolume+="\"}
if(Test-Path $vssAccessLink){(Get-Item $vssAccessLink).Delete()}

write-host "Initiating VSS snapshot..."
$shadowCopyClass=[WMICLASS]"root\cimv2:win32_shadowcopy"
$thisSnapshot = $shadowCopyClass.Create($targetVolume, "ClientAccessible")
$thisShadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $thisSnapshot.ShadowID }
$thisShadowPath = $thisShadow.DeviceObject + "\"

# Creating symlink
$null=cd C:
$null=cmd /c mklink /d $vssAccessLink $thisShadowPath
write-host "Vss Snapshot of $targetVolume has been made and it's accessible at this local file system (LFS): $vssAccessLink."

# Validation
if(Test-Path $vssAccessLink){
$snapshotId=$thisShadow.ID;
write-host "Snapshot $snapshotId has been created.";
return $snapshotId;
}else{
write-host "Failed to create client accessible VSS Snapshot.";
return $false;
}
}

createVssSnapshot $targetVolume $vssAccessLink
  • Copy data from snapshot to new volume
$vssAccessLink="C:\shadowcopy"
$newVolume='X:\'
robocopy $vssAccessLink $newVolume /e /R:0 /NP
  • Change the drive letter of the new volume as the original volume’s drive letter

 

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