PowerShell: Activate Remote Windows

$remoteWindows="SHERVER01","SHERVER02"
$licenseKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"

function activateWindows{
param(
[string]$key
)
$licensingService = get-wmiObject -query "select * from SoftwareLicensingService" -computername $env:computername;
$licensingService.InstallProductKey($key);
sleep 20;
$licensingService.RefreshLicenseStatus();
Get-CimInstance -ClassName SoftwareLicensingProduct|where {$_.PartialProductKey}|select Description, LicenseStatus
}

Invoke-Command -computer $remoteWindows -ScriptBlock{
param($importedFunc,$importedKey)
[ScriptBlock]::Create($importedFunc).Invoke($importedKey);
} -Args ${function:activateWindows},$licenseKey

#Validate licensing status
Get-CimInstance -computername $remoteWindows -ClassName SoftwareLicensingProduct|where {$_.PartialProductKey}|select Description, LicenseStatus

<# Sample Output
Description LicenseStatus
----------- -------------
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
#>

PowerShell: Execute as System Elevated Session with a Domain Administrator account

Part 1: Relaunch script in the context of Elevated System privileges:

############# Ensure that Program executes in the context of a Local System Administrator ################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$currentUser=$myWindowsID.Name
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

function validateCurrentAccountAsDomainAdmin{
if((whoami /groups) -match 'domain admins'){
#"This account is a Domain Admins member";
return $True;
}else{
#"This account is NOT a Domain Admins member";
return $False;
}
}

Write-Host -NoNewLine "Running as $currentUser in context of Elevated System Permissions..."
Write-Host "$currentUser $(if(validateCurrentAccountAsDomainAdmin){"is"}else{"not"}) a Domain Admin."
##########################################################################################################

############# Ensure that Program executes in the context of a Domain Administrator ######################

Part 2: Obtain domain admin $CRED for subsequent Invoke-Command operations:

############# Ensure that Program executes in the context of a Domain Administrator ######################

# This function is will export a global variable $domainAdminCred of a Domain Administrator account
Function getDomainAdminCred{

# Add pre-requisites and obtain list of domain admins
if (!(Get-Module ActiveDirectory)){
Import-Module ServerManager;
Add-WindowsFeature RSAT-AD-PowerShell;
Import-Module ActiveDirectory;
}

# Set domain variables
$currentUser=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userDomain=$env:USERDOMAIN
$domainObject = "LDAP://" + $env:userdnsdomain
$domainAdmins=Get-ADGroupMember -Identity "Domain Admins" -Recursive | %{Get-ADUser -Identity $_.distinguishedName} | Where-Object {$_.Enabled -eq $True}|%{"$userDomain\$($_.SamAccountName)"}

# Check whether a given username matches the list of Domain Admins
function validateDomainAdminMembership{
param (
[string]$username=$currentUser
)
if($domainadmins|?{$_ -eq $username}){
Write-Host "$username is a Domain Admin";
return $True;
}else{
Write-Host "$username not a Domain Admin.";
return $False;
}
}

function testCredential{
param (
[string]$username=$currentUser,
$encryptedPassword=$currentUserPassword
)
$plaintextPassword = (New-Object System.Management.Automation.PSCredential 'N/A',$encryptedPassword).GetNetworkCredential().Password
$domainBindTest = (New-Object System.DirectoryServices.DirectoryEntry($domainObject,$username,$plaintextPassword)).DistinguishedName
if ($domainBindTest){return $True;} else{Return $False;}
}

function validateDomainAdminCred{
$global:domainAdminCred=$False;

function loopUntilCred{
# Get the domain admin account name
do {
$newID=Read-Host -Prompt 'Input a domain admin username'
if (!($newID -match $userDomain)){$newID="$userDomain\$newID"} # Add domain prefix to userID if input doesn't already contain such
$domainAdminMatched=validateDomainAdminMembership $newID;
} until ($domainAdminMatched)

# Get the correct password
do {
$newPassword = Read-Host -assecurestring "Please enter the password for $newID"
#$providedPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
#$providedCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $providedID,$providedPassword
$goodCredential=testCredential -username $newID -encryptedPassword $newPassword
if($goodCredential){
"Alternate Domain Admin Credential validated!";
$global:domainAdminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $newID,$newPassword;
}
else{
"Password doesn't match.";
}
} until($goodCredential)
}

if (validateDomainAdminMembership){
$currentUserPassword=Read-Host -assecurestring "Please enter the current user's password";
if(testCredential -encryptedPassword $currentUserPassword){
"Domain Admin cred with current account has been validated!";
$global:domainAdminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $currentUser,$currentUserPassword;
}else{loopUntilCred;}
}else{
loopUntilCred;
}
}
validateDomainAdminCred;
}

function computerIsDomainJoined{
if ((gwmi win32_computersystem).partofdomain -eq $true) {
write-host -fore green "$ENV:computername is domain joined!"
return $true;
} else {
write-host -fore red "$ENV:computername is on a workgroup!"
return $false;
}
}

if (computerIsDomainJoined){
getDomainAdminCred;
}else{"This computer is not joined to a domain. Thus no Domain Admin credentials are expected."}
##########################################################################################################

PowerShell: Update Windows Computers

<# Update-Windows-Computers-v0.11.ps1
#
# Purpose: trigger Microsoft Updates on a list of Windows machines. Reboot each computer when necessary.
#
# Assumptions:
# - All computers in the list are joined to the same domain
# - Remote Powershell (WinRM) is accessible at the target systems
#
# Workflow:
# - Run in the context of a System Elevated session (Administrator)
# - Check whether the computer is domain joined
# - Validate the current user as a domain admin. If not, obtain domain admin credentials from the user.
# - Retrieve a list of computer names from a text file located in the same directory as this script
# - Prompt the user on whether to perform Windows update on the local system, remote system, or all [listed & local]
#>

############# Ensure that Program executes in the context of a Local System Administrator ################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$currentUser=$myWindowsID.Name
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

function validateCurrentAccountAsDomainAdmin{
if((whoami /groups) -match 'domain admins'){
#"This account is a Domain Admins member";
return $True;
}else{
#"This account is NOT a Domain Admins member";
return $False;
}
}

Write-Host -NoNewLine "Running as $currentUser in context of Elevated System Permissions..."
Write-Host "$currentUser $(if(validateCurrentAccountAsDomainAdmin){"is"}else{"not"}) a Domain Admin."
##########################################################################################################

############# Ensure that Program executes in the context of a Domain Administrator ######################

# This function is will export a global variable $domainAdminCred of a Domain Administrator account
Function getDomainAdminCred{

# Add pre-requisites and obtain list of domain admins
if (!(Get-Module ActiveDirectory)){
Import-Module ServerManager;
Add-WindowsFeature RSAT-AD-PowerShell;
Import-Module ActiveDirectory;
}

# Set domain variables
$currentUser=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userDomain=$env:USERDOMAIN
$domainObject = "LDAP://" + $env:userdnsdomain
$domainAdmins=Get-ADGroupMember -Identity "Domain Admins" -Recursive | %{Get-ADUser -Identity $_.distinguishedName} | Where-Object {$_.Enabled -eq $True}|%{"$userDomain\$($_.SamAccountName)"}

# Check whether a given username matches the list of Domain Admins
function validateDomainAdminMembership{
param (
[string]$username=$currentUser
)
if($domainadmins|?{$_ -eq $username}){
Write-Host "$username is a Domain Admin";
return $True;
}else{
Write-Host "$username not a Domain Admin.";
return $False;
}
}

function testCredential{
param (
[string]$username=$currentUser,
$encryptedPassword=$currentUserPassword
)
$plaintextPassword = (New-Object System.Management.Automation.PSCredential 'N/A',$encryptedPassword).GetNetworkCredential().Password
$domainBindTest = (New-Object System.DirectoryServices.DirectoryEntry($domainObject,$username,$plaintextPassword)).DistinguishedName
if ($domainBindTest){return $True;} else{Return $False;}
}

function validateDomainAdminCred{
$global:domainAdminCred=$False;

function loopUntilCred{
# Get the domain admin account name
do {
$newID=Read-Host -Prompt 'Input a domain admin username'
if (!($newID -match $userDomain)){$newID="$userDomain\$newID"} # Add domain prefix to userID if input doesn't already contain such
$domainAdminMatched=validateDomainAdminMembership $newID;
} until ($domainAdminMatched)

# Get the correct password
do {
$newPassword = Read-Host -assecurestring "Please enter the password for $newID"
#$providedPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
#$providedCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $providedID,$providedPassword
$goodCredential=testCredential -username $newID -encryptedPassword $newPassword
if($goodCredential){
"Alternate Domain Admin Credential validated!";
$global:domainAdminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $newID,$newPassword;
}
else{
"Password doesn't match.";
}
} until($goodCredential)
}

if (validateDomainAdminMembership){
$currentUserPassword=Read-Host -assecurestring "Please enter the current user's password";
if(testCredential -encryptedPassword $currentUserPassword){
"Domain Admin cred with current account has been validated!";
$GLOBAL:domainAdminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $currentUser,$currentUserPassword;
}else{loopUntilCred;}
}else{
loopUntilCred;
}
}
validateDomainAdminCred;
return $domainAdminCred;
}

function computerIsDomainJoined{
if ((gwmi win32_computersystem).partofdomain -eq $true) {
write-host -fore green "$ENV:computername is domain joined!"
return $true;
} else {
write-host -fore red "$ENV:computername is on a workgroup!"
return $false;
}
}

if (computerIsDomainJoined){
getDomainAdminCred;
}else{"This computer is not joined to a domain. Thus no Domain Admin credentials are expected."}
##########################################################################################################

# Get script path
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName

# Update this list here via direct text editing or change it to pull a prepared text/csv file
#$computersList=Get-Content -Path "$scriptPath\computerslist.txt"
$computersList="SHERVER01","SHERVER02","SHERVER03","SHERVER04"

# Remove this computer from the remote computers list, if exists
$thisComputer=$ENV:computername
$remoteComputers=$computersList|?{$_ -ne $thisComputer}

function updateLocalWindows{
# Prerequisites
function installPrerequisites{
#Import-Module PSWindowsUpdate -force;
#$psWindowsUpdateAvailable=Get-Module PSWindowsUpdate -EA SilentlyContinue;
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
}
catch{
"Prerequisites not met on $computer.";
}
}
}

function checkPendingReboot{
param([string]$computer=$ENV:computername)

function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}

$localhost=$ENV:computername
if ($localHost -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -ComputerName $computer -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}
installPrerequisites;

# Register the user of Windows Update Service if it has not been registered
$MicrosoftUpdateID="7971f918-a847-4430-9279-4a52d1efe18d"
$registered=$MicrosoftUpdateID -in (Get-WUServiceManager).ServiceID
if (!($registered)){
Add-WUServiceManager -ServiceID 7971f918-a847-4430-9279-4a52d1efe18d -Confirm:$false
}

# Perform Updates
Get-WindowsUpdate -AcceptAll -MicrosoftUpdate -Install -IgnoreReboot;

if (checkPendingReboot){
$warning="There is a pending reboot flag on this host.`n"
$prompt="Please type 'exit' to cancel or 'reboot' to reboot"
$warning;
do{
$userInput=Read-Host -Prompt $prompt;
if ($userInput -match "reboot"){"Restarting command received!";Restart-Computer;} # -match is faster than -like
}while (($userInput -notmatch "reboot") -AND ($userInput -notmatch "(quit|cancel|exit)"))
}else{"Done."}
}

function listPendingUpdates{
#[DateTime]$last24Hours=(Get-Date).AddHours(-24)
param([string]$computername = $env:COMPUTERNAME)

"Checking computer $computername`:"
$updatesession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$computername))
$UpdateSearcher = $updatesession.CreateUpdateSearcher()
$searchresult = $updatesearcher.Search("IsInstalled=0") # 0 = NotInstalled | 1 = Installed

$updates = If ($searchresult.Updates.Count -gt 0) {
$count = $searchresult.Updates.Count
For ($i=0; $i -lt $count; $i++) {
#$timeStamp=[DateTime]$item.LastDeploymentChangeTime
$item = $searchresult.Updates.Item($i)
#if ($timeStamp -gt $last24Hours){
#Create new custom object
[pscustomobject]@{
Title = $item.Title
KB = $item| foreach {$_.KbArticleids}
Severity = $item.MsrcSeverity
IsDownloaded=$item.IsDownloaded
SecurityBulletin = $($item.SecurityBulletinIDs)
Url = $item.MoreInfoUrls
Categories = ($item.Categories | Select-Object -ExpandProperty Name)
BundledUpdates = @($item.BundledUpdates)|ForEach{
[pscustomobject]@{
Title = $_.Title
DownloadUrl = @($_.DownloadContents).DownloadUrl
}

}
}#pscustomobject
#}#if-timeStamp
}#forloop
}#updates

if($updates){$updates |ft kb,title,severity,IsDownloaded -autosize}else{"No pending updates."}
}

function updateRemoteWindows{
[CmdletBinding()]
param (
[parameter(Mandatory=$true,Position=0)]
[string[]]$computer=$ENV:computername
)

<#
.SYNOPSIS
This script will automatically install all avaialable windows updates on a device and will automatically reboot if needed, after reboot, windows updates will continue to run until no more updates are available.
.PARAMETER URL
User the Computer parameter to specify the Computer to remotely install windows updates on.
# Around 40% of this function was forked from https://www.altaro.com/msp-dojo/powershell-windows-updates/
# Partial credit: Luke Orellana
# Note: I've added more features:
# - Check WSUS settings to account for that scenario
# - Prepare the targets by installing prerequisites prior to proceeding further to preemptively resolve dependency errors
# - Include additional dedendencies such as TLS1.2, Nuget & PSGALLERY
# - Check if server needs a reboot before issuing the reboot command instead of just inadvertently trigger reboots
# - Fixed the blank lines in output log causing bug in status query
# - More thorough cleanup routine
# - Detect and handle proxies (in development)
#>

function installPrerequisites{
#Import-Module PSWindowsUpdate -force;
#$psWindowsUpdateAvailable=Get-Module PSWindowsUpdate -EA SilentlyContinue;
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
}
catch{
"Prerequisites not met on $ENV:COMPUTER.";
}
}
}

function checkPendingReboot{
param([string]$computer=$ENV:computername)

function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}

$localhost=$ENV:computername
if ($localHost -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -ComputerName $computer -Credential $domainAdminCred -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}

# Function requires 2 parameters: -computerName and -processName
function killProcess{
[CmdletBinding()]
param(
[string[]]$computerName=$ENV:COMPUTERNAME,
[parameter(Mandatory=$true)][string]$executableName="powershell.exe"
)

# WMI querying method
$processes = Get-WmiObject -Class Win32_Process -ComputerName $ComputerName -Credential $domainAdminCred -Filter "name='$executableName'"

if ($processes){
foreach ($process in $processes) {
$terminationResult = $process.terminate()
$processid = $process.handle

if($terminationResult.returnvalue -eq 0) {
write-host "The process $executableName `($processid`) terminated successfully"
} else {
write-host "The process $executableName `($processid`) termination has some problems"
}
}
}else{
"$executableName is currently not running on $computerName."
}
}

# installPrerequisites on remote machine
<#
invoke-command -computername $computer -Credential $domainAdminCred -scriptblock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -Args ${function:installPrerequisites};
#>

Do{
#starts up a remote powershell session to the computer
do{
$session = New-PSSession -ComputerName $computer -Credential $domainAdminCred
"Connecting to remote computer $computer..."
sleep -seconds 5
if ($session){"Connected."}
} until ($session.state -match "Opened")

# Install prerequisites
invoke-command -session $session -scriptblock { param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -Args ${function:installPrerequisites};

#retrieves a list of available updates
"Checking for new updates on $computer"
$updates = invoke-command -session $session -scriptblock {Get-wulist -verbose}

# Count how many updates are available
$updatesCount = ($updates.kb).count

function checkWsus{
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;
if($wuIsOn){$GLOBAL:wsus=$True}else{$GLOBAL:wsus=$False}
}

function turnoffWsus{
# Turn WSUS settings OFF temporarily...
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
setRegKey -path $wuPath -name $wuKey -value 0;
restart-service wuauserv;
}

function turnonWsus{
# Turning WSUS settings back to ON status
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
setRegKey -path $wuPath -name $wuKey -value 1;
restart-service wuauserv;
}

# If there are available updates proceed with installing the updates and then reboot the remote machine if required
if ($updates -ne $null){

checkWsus;
if($wsus){turnoffWsus;}
# Create a scheduled task on remote computer name PSWindowsUpdate with this script
<# experimental: currently, this prevented scheduled task from executing
$invokeScript = {
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;

if($wuIsOn){
# Perform updates by manipulating WSUS keys
# Turn WSUS settings OFF temporarily...
setRegKey -path $wuPath -name $wuKey -value 0;
restart-service wuauserv;
Import-Module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log;
# Turning WSUS settings back to ON status
setRegKey -path $wuPath -name $wuKey -value 1;
restart-service wuauserv;
}else{
"Triggering Updates...";
Import-Module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log;
(gc C:\PSWindowsUpdate.log) | ? {$_.trim() -ne "" } | set-content C:\PSWindowsUpdate.log;
}
}
#>
#Invoke-WUJob will insert a scheduled task on the remote target as a mean to bypass 2nd hop issues
$invokeScript = {import-module PSWindowsUpdate; Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log}
$job=Invoke-WUjob -ComputerName $computer -Credential $domainAdminCred -Script $invokeScript -Confirm:$false -RunNow
#[void]$job;
<# Troubleshooting
ERROR:
Invoke-WUjob : [SERVER] Connecting to remote server SERVER failed with the following error message :
The WinRM client cannot process the request. Default credentials with Negotiate over HTTP can be used only if the
target machine is part of the TrustedHosts list or the Allow implicit credentials for Negotiate option is specified.
For more information, see the about_Remote_Troubleshooting Help topic.
At line:49 char:9
+ Invoke-WUjob -ComputerName $computer -Script $Script -Confirm ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (PRDFCL01DC1SV1:String) [Invoke-WUJob], PSRemotingTransportException
+ FullyQualifiedErrorId : ExplicitCredentialsRequired,PSSessionStateBroken


Resolution:
1. Option one is to ignore this error. No actions are required as this error appears to have no effects
on Invoke-WUjob's intended scheduled task creation.
Hence, the remote updating program proceeds as normal.
2. Option two is to fix the targets using this information

-----------------------------------------------------
HOW TO USE AN IP ADDRESS IN A REMOTE COMMAND
-----------------------------------------------------
ERROR: The WinRM client cannot process the request. If the
authentication scheme is different from Kerberos, or if the client
computer is not joined to a domain, then HTTPS transport must be used
or the destination machine must be added to the TrustedHosts
configuration setting.

The ComputerName parameters of the New-PSSession, Enter-PSSession and
Invoke-Command cmdlets accept an IP address as a valid value. However,
because Kerberos authentication does not support IP addresses, NTLM
authentication is used by default whenever you specify an IP address.

When using NTLM authentication, the following procedure is required
for remoting.

1. Configure the computer for HTTPS transport or add the IP addresses
of the remote computers to the TrustedHosts list on the local
computer.

For instructions, see "How to Add a Computer to the TrustedHosts
List" below.


2. Use the Credential parameter in all remote commands.

This is required even when you are submitting the credentials
of the current user.
#>

#Show update status until the amount of installed updates equals the same as the count of updates being reported
sleep -Seconds 30 # Wait for the log file to generate
$dots=80
$dotsCount=0
$lastActivity="";
$installedCount=0;
Write-Host "There is/are $updatesCount pending update(s)`n";

do {
$updatestatus = Get-Content "\\$computer\c$\PSWindowsUpdate.log"
$currentActivity=$updatestatus | select-object -last 1

if (($currentActivity -ne $lastActivity) -AND ($currentActivity -ne $Null)){
Write-Host "Procesing $($installedCount+1) of $updatesCount updates."
Write-Host "`n$currentActivity";
$lastActivity=$currentActivity;
}else{
if ($dotCount++ -le $dots){
Write-Host -NoNewline ".";
if($installedCount -eq $updatesCount){Write-Host "Processing last update: $installedCount of $updatesCount."}
}else{
Write-Host ".";
$dotCount=0;
}
}
sleep -Seconds 10
$ErrorActionPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Continue'
$installedCount = ([regex]::Matches($updatestatus, "Installed")).count
}until ($installedCount -eq $updatesCount)

#restarts the remote computer and waits till it starts up again
if (checkPendingReboot $computer){
"`nReboots required.`nRestarting remote computer $computer to clear pending reboot flags."
Restart-Computer -Wait -ComputerName $computer -Force;
"$computer has been restarted."
}else{"No reboots required."}
}
}until(($updates -eq $null) -OR ($installedCount -eq $updatesCount))

function cleanup{
if(test-netconnection -ComputerName $computer -port 445 -ErrorAction SilentlyContinue){
if(Get-Process -ComputerName $computer powershell -ErrorAction SilentlyContinue){
Write-Host "Terminating any powershell.exe processes."
killProcess -ComputerName $computer -ExecutableName powershell.exe
}
}

if (Test-Path $logFile -ErrorAction SilentlyContinue){
Write-Host "Deleting log to prevent collisions with subsequent runs."
$logFile="\\$computer\c$\PSWindowsUpdate.log"
Write-Host "Removing $logFile."
Remove-item $logFile | Out-Null
}
Write-Host "Removing the Invoke-WuJob's remnant, PSWindowsUpdate scheduled task, from $computer."
invoke-command -computername $computer -Credential $domainAdminCred -ScriptBlock {
if (Get-ScheduledTask -TaskName "PSWindowsUpdate" -ErrorAction SilentlyContinue){
Unregister-ScheduledTask -TaskName PSWindowsUpdate -Confirm:$false} | Out-Null;
}
}
cleanup;

# Revert WSUS registry edits, if any
if($wsus){turnonWsus;}

Write-Host "Windows is now up to date on $computer";
<# Check scheduled task of remote computer in case of simultaneous updating
$task=Invoke-Command -ComputerName $computer -ScriptBlock{(Get-ScheduledTask -TaskName "PSWindowsUpdate").State}
if ($task.State -eq "Running"){"Still Running..."}
#>
}

function updateComputers {

# Experimental
function simultaneousUpdates{
# Start jobs on multiple targets simultaneously
$maxConcurrentJobs=8
ForEach ($server in $remoteComputers) {
"Starting process on $server..."
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le $maxConcurrentJobs) {
Start-Job -ScriptBlock{
param($importedFunc,$node);
[ScriptBlock]::Create($importedFunc).Invoke($node);
} -ArgumentList ${function:updateRemoteWindows},$server
}else{
$running | Wait-Job
}
Get-Job | Receive-Job
}

# Update local Windows as soon as remote jobs are completed
$jobsCount = @(Get-Job | Where-Object { $_.State -eq 'Running' }).Count
if ($jobsCount -eq 0) {
updateLocalWindows;
}else{$running | Wait-Job}

<#
# Process all nodes at once
$remoteComputers | %{
# Define what each job does
$script = {
param($Arg0);
invoke-command -computername $Arg0 -scriptBlock{hostname}
#updateRemoteWindowsAutoReboot $server
Start-Sleep 10;
}

# Execute the jobs in parallel
Start-Job $script -ArgumentList $_
}

Get-Job

# Check every X seconds and wait for all jobs to complete
While (Get-Job -State "Running"){Start-Sleep 10;}

# Getting the information back from the jobs. A one-time trigger.
Get-Job | Receive-Job
#>

<# invoke command on a list of servers
Invoke-Command -computername $remoteComputers -ScriptBlock {
param($updateWindows)
[ScriptBlock]::Create($updateWindows).Invoke()
} -Args ${function:applyWindowsUpdates} -ThrottleLimit 1
#>
}

# Validation of prior updates
function validateUpdates{
$remoteComputers|%{invoke-command -computername $_ -Credential $domainAdminCred -scriptblock{
param($importedFunc,$name);
[ScriptBlock]::Create($importedFunc).Invoke($name);
} -Args ${function:listPendingUpdates},$_
}
listPendingUpdates;
}

$warning="This program will forcefully update all computers on the list and automatically reboot them whenever necessary.`n"
$prompt="Please type 'local','remote', 'all','simultaneous' or 'exit' to end"
$warning;
do{
$userInput=Read-Host -Prompt $prompt;
if ($userInput -match "local*"){"Local!";updateLocalWindows;} # -match is faster than -like
if ($userInput -match "remote*"){"Remote!";$remoteComputers|%{updateRemoteWindows -computer $_;};}
if ($userInput -match "all*"){"All!";$remoteComputers|%{updateRemoteWindows $_;};updateLocalWindows;}
if ($userInput -match "simultaneous*"){"Simultaneous!";simultaneousUpdates;}
}while (($userInput -notmatch "(local|remote|all|simultaneous)") -AND ($userInput -notmatch "(end|quit|cancel|exit)"))
validateUpdates;
}

updateComputers;
# Update-Windows-Computers-v0.1.ps1

# Purpose: trigger Microsoft Updates on a list of Windows machines. Reboot each computer when necessary.

# Update this list here via direct text editing or change it to pull a prepared text/csv file
$computerList="SHERVER01","SHERVER02","SHERVER03","SHERVER04"

# Update this list here via direct text editing or change it to pull a prepared text/csv file
#$computerList="SHERVER01","SHERVER02","SHERVER03","SHERVER04"

# Remove this computer from the remote computers list, if exists
$thisComputer=$ENV:computername
$remoteComputers=$computerList|?{$_ -ne $thisComputer}

<#
$currentUser=[Security.Principal.WindowsIdentity]::GetCurrent().Name
$plainTextPassword=Read-Host -Prompt "Input the password for $currentUser"
$password=ConvertTo-SecureString -String $plainTextPassword -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $currentUser,$password
#>

function updateLocalWindows{
# Prerequisites
function installPrerequisites{
#Import-Module PSWindowsUpdate -force;
#$psWindowsUpdateAvailable=Get-Module PSWindowsUpdate -InformationAction SilentlyContinue;
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -InformationAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
}
catch{
"Prerequisites not met on $computer.";
}
}
}

function checkPendingReboot{
param([string]$computer=$ENV:computername)

function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}

$localhost=$ENV:computername
if ($localHost -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -ComputerName $computer -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}
installPrerequisites;

# Register the user of Windows Update Service if it has not been registered
$MicrosoftUpdateID="7971f918-a847-4430-9279-4a52d1efe18d"
$registered=$MicrosoftUpdateID -in (Get-WUServiceManager).ServiceID
if (!($registered)){
Add-WUServiceManager -ServiceID 7971f918-a847-4430-9279-4a52d1efe18d -Confirm:$false
}

# Perform Updates
Get-WindowsUpdate -AcceptAll -MicrosoftUpdate -Install -IgnoreReboot;

if (checkPendingReboot){
$warning="There is a pending reboot flag on this host.`n"
$prompt="Please type 'exit' to cancel or 'reboot' to reboot"
$warning;
do{
$userInput=Read-Host -Prompt $prompt;
if ($userInput -match "reboot"){"Restarting command received!";Restart-Computer;} # -match is faster than -like
}while (($userInput -notmatch "reboot") -AND ($userInput -notmatch "(quit|cancel|exit)"))
}else{"Done."}
}

function listPendingUpdates{
#[DateTime]$last24Hours=(Get-Date).AddHours(-24)
param([string]$computername = $env:COMPUTERNAME)

"Checking computer $computername`:"
$updatesession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$computername))
$UpdateSearcher = $updatesession.CreateUpdateSearcher()
$searchresult = $updatesearcher.Search("IsInstalled=0") # 0 = NotInstalled | 1 = Installed

$updates = If ($searchresult.Updates.Count -gt 0) {
$count = $searchresult.Updates.Count
For ($i=0; $i -lt $count; $i++) {
#$timeStamp=[DateTime]$item.LastDeploymentChangeTime
$item = $searchresult.Updates.Item($i)
#if ($timeStamp -gt $last24Hours){
#Create new custom object
[pscustomobject]@{
Title = $item.Title
KB = $item| foreach {$_.KbArticleids}
Severity = $item.MsrcSeverity
IsDownloaded=$item.IsDownloaded
SecurityBulletin = $($item.SecurityBulletinIDs)
Url = $item.MoreInfoUrls
Categories = ($item.Categories | Select-Object -ExpandProperty Name)
BundledUpdates = @($item.BundledUpdates)|ForEach{
[pscustomobject]@{
Title = $_.Title
DownloadUrl = @($_.DownloadContents).DownloadUrl
}

}
}#pscustomobject
#}#if-timeStamp
}#forloop
}#updates

if($updates){$updates |ft kb,title,severity,IsDownloaded -autosize}else{"No pending updates."}
}

function updateRemoteWindows{
<#
.SYNOPSIS
This script will automatically install all avaialable windows updates on a device and will automatically reboot if needed, after reboot, windows updates will continue to run until no more updates are available.
.PARAMETER URL
User the Computer parameter to specify the Computer to remotely install windows updates on.
# Around 40% of this function was forked from https://www.altaro.com/msp-dojo/powershell-windows-updates/
# Partial credit: Luke Orellana
# Note: I've added more features:
# - Check WSUS settings to account for that scenario
# - Prepare the targets by installing prerequisites prior to proceeding further to preemptively resolve dependency errors
# - Include additional dedendencies such as TLS1.2, Nuget & PSGALLERY
# - Check if server needs a reboot before issuing the reboot command instead of just inadvertently trigger reboots
# - Fixed the blank lines in output log causing bug in status query
# - More thorough cleanup routine
# - Detect and handle proxies (in development)
#>

[CmdletBinding()]
param (
[parameter(Mandatory=$true,Position=0)]
[string[]]$computer=$ENV:computername
)

function installPrerequisites{
#Import-Module PSWindowsUpdate -force;
#$psWindowsUpdateAvailable=Get-Module PSWindowsUpdate -InformationAction SilentlyContinue;
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -InformationAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
}
catch{
"Prerequisites not met on $computer.";
}
}
}

function checkPendingReboot{
param([string]$computer=$ENV:computername)

function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}

$localhost=$ENV:computername
if ($localHost -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -ComputerName $computer -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}

# Function requires 2 parameters: -computerName and -processName
function killProcess{
[CmdletBinding()]
param(
[string[]]$computerName=$ENV:COMPUTERNAME,
[parameter(Mandatory=$true)][string]$executableName="powershell.exe"
)

# WMI querying method
$processes = Get-WmiObject -Class Win32_Process -ComputerName $ComputerName -Filter "name='$executableName'"

if ($processes){
foreach ($process in $processes) {
$terminationResult = $process.terminate()
$processid = $process.handle

if($terminationResult.returnvalue -eq 0) {
write-host "The process $executableName `($processid`) terminated successfully"
} else {
write-host "The process $executableName `($processid`) termination has some problems"
}
}
}else{
"$executableName is currently not running on $computerName."
}
}

installPrerequisites;

Do{
#starts up a remote powershell session to the computer
do{
$session = New-PSSession -ComputerName $computer #-Credential $cred
"Connecting to remote computer $computer"
sleep -seconds 10
} until ($session.state -match "Opened")

invoke-command -computername $computer -scriptblock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -Args ${function:installPrerequisites};

#retrieves a list of available updates
"Checking for new updates on $computer"
$updates = invoke-command -session $session -scriptblock {Get-wulist #-verbose}

# Count how many updates are available
$updatesCount = ($updates.kb).count

function checkWsus{
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;
if($wuIsOn){$GLOBAL:wsus=$True}else{$GLOBAL:wsus=$False}
}

function turnoffWsus{
# Turn WSUS settings OFF temporarily...
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
setRegKey -path $wuPath -name $wuKey -value 0;
restart-service wuauserv;
}

function turnonWsus{
# Turning WSUS settings back to ON status
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
setRegKey -path $wuPath -name $wuKey -value 1;
restart-service wuauserv;
}

# If there are available updates proceed with installing the updates and then reboot the remote machine if required
if ($updates -ne $null){

checkWsus;
if($wsus){turnoffWsus;}
# Create a scheduled task on remote computer name PSWindowsUpdate with this script
<# experimental: currently, this prevented scheduled task from executing
$invokeScript = {
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;

if($wuIsOn){
# Perform updates by manipulating WSUS keys
# Turn WSUS settings OFF temporarily...
setRegKey -path $wuPath -name $wuKey -value 0;
restart-service wuauserv;
Import-Module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log;
# Turning WSUS settings back to ON status
setRegKey -path $wuPath -name $wuKey -value 1;
restart-service wuauserv;
}else{
"Triggering Updates...";
Import-Module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log;
(gc C:\PSWindowsUpdate.log) | ? {$_.trim() -ne "" } | set-content C:\PSWindowsUpdate.log;
}
}
#>
#Invoke-WUJob will insert a scheduled task on the remote target as a mean to bypass 2nd hop issues
$invokeScript = {import-module PSWindowsUpdate; Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log}
$job=Invoke-WUjob -ComputerName $computer -Script $invokeScript -Confirm:$false -RunNow
#[void]$job;
<# Troubleshooting
ERROR:
Invoke-WUjob : [SERVER] Connecting to remote server SERVER failed with the following error message :
The WinRM client cannot process the request. Default credentials with Negotiate over HTTP can be used only if the
target machine is part of the TrustedHosts list or the Allow implicit credentials for Negotiate option is specified.
For more information, see the about_Remote_Troubleshooting Help topic.
At line:49 char:9
+ Invoke-WUjob -ComputerName $computer -Script $Script -Confirm ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (PRDFCL01DC1SV1:String) [Invoke-WUJob], PSRemotingTransportException
+ FullyQualifiedErrorId : ExplicitCredentialsRequired,PSSessionStateBroken


Resolution:
1. Option one is to ignore this error. No actions are required as this error appears to have no effects
on Invoke-WUjob's intended scheduled task creation.
Hence, the remote updating program proceeds as normal.
2. Option two is to fix the targets using this information

-----------------------------------------------------
HOW TO USE AN IP ADDRESS IN A REMOTE COMMAND
-----------------------------------------------------
ERROR: The WinRM client cannot process the request. If the
authentication scheme is different from Kerberos, or if the client
computer is not joined to a domain, then HTTPS transport must be used
or the destination machine must be added to the TrustedHosts
configuration setting.

The ComputerName parameters of the New-PSSession, Enter-PSSession and
Invoke-Command cmdlets accept an IP address as a valid value. However,
because Kerberos authentication does not support IP addresses, NTLM
authentication is used by default whenever you specify an IP address.

When using NTLM authentication, the following procedure is required
for remoting.

1. Configure the computer for HTTPS transport or add the IP addresses
of the remote computers to the TrustedHosts list on the local
computer.

For instructions, see "How to Add a Computer to the TrustedHosts
List" below.


2. Use the Credential parameter in all remote commands.

This is required even when you are submitting the credentials
of the current user.
#>

#Show update status until the amount of installed updates equals the same as the count of updates being reported
sleep -Seconds 30 # Wait for the log file to generate
$dots=80
$dotsCount=0
$lastActivity="";
$installedCount=0;
Write-Host "There is/are $updatesCount pending update(s)`n";

do {
$updatestatus = Get-Content $logFile
$currentActivity=$updatestatus | select-object -last 1
if (($currentActivity -ne $lastActivity) -AND ($currentActivity -isNot $Null)){
Write-Host "Procesing $installedCount of $updatesCount updates."
Write-Host "`n $currentActivity";
$lastActivity=$currentActivity;
}else{
if ($dotCount++ -le $dots){
Write-Host -NoNewline ".";
}else{
Write-Host ".";
$dotCount=0;
}
}
sleep -Seconds 10
$ErrorActionPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Continue'
$installedCount = ([regex]::Matches($updatestatus, "Installed")).count
}until ($installedCount -eq $updatesCount)

#restarts the remote computer and waits till it starts up again
if (checkPendingReboot $computer){
"`nReboots required.`nRestarting remote computer $computer to clear pending reboot flags."
Restart-Computer -Wait -ComputerName $computer -Force
}else{"No reboots required."}
}
}
}until(($updates -eq $null) -OR ($installedCount -eq $updatesCount))

function cleanup{
# Delete log to prevent collisions with subsequent runs
$logFile="\\$computer\c$\PSWindowsUpdate.log"
if (Test-Path $logFile -ErrorAction SilentlyContinue){
Write-Host "Terminating any powershell.exe processes."
killProcess -ComputerName $computer -ExecutableName powershell.exe
Write-Host "Removing $logFile."
Remove-item $logFile | Out-Null

# Cleanup: remove PSWindowsUpdate scheduled task from computer
invoke-command -computername $computer -ScriptBlock {Unregister-ScheduledTask -TaskName PSWindowsUpdate -Confirm:$false} | Out-Null
}
}
cleanup;

# Revert WSUS registry edits, if any
if($wsus){turnonWsus;}

Write-Host "Windows is now up to date on $computer";
<# Check scheduled task of remote computer in case of simultaneous updating
$task=Invoke-Command -ComputerName $computer -ScriptBlock{(Get-ScheduledTask -TaskName "PSWindowsUpdate").State}
if ($task.State -eq "Running"){"Still Running..."}
#>
}

function updateComputers {

function sequentialUpdates{
# Processing updates
$remoteComputers|%{updateRemoteWindows $_;}
updateLocalWindows;
}

function simultaneousUpdates{
# Start jobs on multiple targets simultaneously
$maxConcurrentJobs=8
ForEach ($server in $remoteComputers) {
"Starting process on $server..."
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le $maxConcurrentJobs) {
Start-Job -ScriptBlock{
param($importedFunc,$node);
[ScriptBlock]::Create($importedFunc).Invoke($node);
} -ArgumentList ${function:updateRemoteWindows},$server
}else{
$running | Wait-Job
}
Get-Job | Receive-Job
}

# Update local Windows as soon as remote jobs are completed
$jobsCount = @(Get-Job | Where-Object { $_.State -eq 'Running' }).Count
if ($jobsCount -eq 0) {
updateLocalWindows;
}else{$running | Wait-Job}

<#
# Process all nodes at once
$remoteComputers | %{
# Define what each job does
$script = {
param($Arg0);
invoke-command -computername $Arg0 -scriptBlock{hostname}
#updateRemoteWindowsAutoReboot $server
Start-Sleep 10;
}

# Execute the jobs in parallel
Start-Job $script -ArgumentList $_
}

Get-Job

# Check every X seconds and wait for all jobs to complete
While (Get-Job -State "Running"){Start-Sleep 10;}

# Getting the information back from the jobs. A one-time trigger.
Get-Job | Receive-Job
#>

<# invoke command on a list of servers
Invoke-Command -computername $remoteComputers -ScriptBlock {
param($updateWindows)
[ScriptBlock]::Create($updateWindows).Invoke()
} -Args ${function:applyWindowsUpdates} -ThrottleLimit 1
#>
}

# Validation of prior updates
function validateUpdates{
$remoteComputers|%{invoke-command -computername $_ -scriptblock{
param($importedFunc,$name);
[ScriptBlock]::Create($importedFunc).Invoke($name);
} -Args ${function:listPendingUpdates},$_
}
listPendingUpdates;
}

$warning="This program will forcefully update all computers on the list and automatically reboot them whenever necessary.`n"
$prompt="Please type 'exit' to cancel or 'I confirm' to proceed"
$warning;
do{
$userInput=Read-Host -Prompt $prompt;
if ($userInput -match "i confirm*"){"Bingo!";sequentialUpdates;} # -match is faster than -like
if ($userInput -match "simultaneous*"){"Simultaneous!";simultaneousUpdates;} # -match is faster than -like
}while (($userInput -notmatch "(i confirm|simultaneous)") -AND ($userInput -notmatch "(quit|cancel|exit)"))
validateUpdates;
}

updateComputers;

PowerShell: Change IP of Remote Windows

# Change-IP-Remote-Windows.ps1

$oldIP="192.168.40.135"
$newIP="192.168.40.136"
$netmask="255.255.255.0"
$gateway="192.168.40.1"
$dnsServers=@("10.10.8.1","10.10.18.1")

function changeIpRemoteComputer{
Param (
[string]$oldIP,
[string]$newIP,
[string]$newSubnetMask = "255.255.255.0",
[string]$newDefaultGateway,
[array]$newDNS = @("8.8.8.8","4.4.2.2")
)

$remoteComputerName=[System.Net.Dns]::GetHostEntry($oldIP).HostName
<# PowerShell 4.0
$wmi = Get-WmiObject -ComputerName $oldIP Win32_NetworkAdapterConfiguration | Where-Object { $_.IPAddress -eq $oldIP }
New-NetIPAddress -InterfaceIndex $wmi.Index - IPAddress $newIP -PrefixLength $newSubnetMask -DefaultGateway $newDefaultGateway
Register-DnsClient;
#>

# Powershell 2.0
function changeIP{
Param (
$oldIP,
$newIP,
$newSubnetMask,
$newDefaultGateway,
$newDNS
)
$wmi = Get-WmiObject win32_networkadapterconfiguration -filter "ipenabled = 'true'" | Where-Object { $_.IpAddress -eq $oldIP }
$wmi.EnableStatic($newIP, $newSubnetMask)
$wmi.SetGateways($newDefaultGateway, 1)
$wmi.SetDNSServerSearchOrder($newDNS)

ipconfig /registerdns;
$validatedComputername=[System.Net.Dns]::GetHostEntry($newIP).HostName;
Write-Host "$validatedComputername now has the ip address of $newIP";
}

Invoke-Command -computerName $remoteComputerName -ScriptBlock{
param($importedFunc,$a,$b,$c,$d,$e);
[ScriptBlock]::Create($importedFunc).Invoke($a,$b,$c,$d,$e);
} -Args ${function:changeIP},$oldIP,$newIP,$newSubnetMask,$newDefaultGateway,$newDNS
}
changeIpRemoteComputer -oldIP $oldIP -newIP $newIP -newSubnetMask $netmask -newDefaultGateway $gateway -newDNS $dnsServers

Sample Output

PS C:\Windows\system32> changeIpRemoteComputer -oldIP $oldIP -newIP $newIP -newSubnetMask $netmask -newDefaultGateway $g
ateway -newDNS $dnsServers
WARNING: The network connection to komputer.something.local has been interrupted. Attempting to reconnect for up to 4
minutes...
WARNING: Attempting to reconnect to komputer.something.local ...
WARNING: The network connection to komputer.something.local has been restored.
komputer.something.local now has the ip address of 192.168.40.135


PSComputerName : komputer.something.local
RunspaceId : e0593b1f-9419-4f78-8a2c-45eb4b47d7de
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0

PSComputerName : komputer.something.local
RunspaceId : e0593b1f-9419-4f78-8a2c-45eb4b47d7de
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0

PSComputerName : komputer.something.local
RunspaceId : e0593b1f-9419-4f78-8a2c-45eb4b47d7de
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0


Windows IP Configuration

Registration of the DNS resource records for all adapters of this computer has been initiated. Any errors will be report
ed in the Event Viewer in 15 minutes.

PowerShell: Kill Processes Remotely

    # This function requires 2 parameters: -computerName and -processName
function killRemoteProcess{
[cmdletbinding()]
param(
[string]$computerName=$env:COMPUTERNAME,
[parameter(Mandatory=$true)]
[string]$processName="powershell.exe"
)

# WMI querying method
$processes = Get-WmiObject -Class Win32_Process -ComputerName $ComputerName -Filter "name='$processName'"

if ($processes){
foreach ($process in $processes) {
$terminationResult = $process.terminate()
$processid = $process.handle

if($terminationResult.returnvalue -eq 0) {
write-host "The process $ProcessName `($processid`) terminated successfully"
} else {
write-host "The process $ProcessName `($processid`) termination has some problems"
}
}
}else{
"$processName not found. Try again ya."
}
}

Sample OutPut

PS C:\Windows\system32> killRemoteProcess -computername $computer -ProcessName powershell.exe
The process powershell.exe (5880) terminated successfully

PowerShell: removing elements of an immutable “fixed size” array

By default, PowerShell’s Array is an object class; hence, its length a “fixed size” as an object. Therefore, any pop, splice, or push methods will not work with Array elements in this language as compared to a other languages such as JavaScript and Python. Here are two workarounds to this issue:

  1. Cast the Array object as a  [System.Collections.ArrayList]$ArrayObject; which will effectively convert the $ArrayObject to a “ArrayList” class. Hence, $ArrayObject.GetType().Namewill return “ArrayList“, instead of “Object[]“. This will make it “mutable” or changeable, whereby methods such as .Add() and .Remove() are available. The only downside to this conversion is that ArrayList cannot be multi-dimensional. Hence, the third item on this list is the catch-all technique in dealing with $ArrayObject ‘s.
  2. Convert $ArrayObject to a $Collection type: $Collection = {$ArrayObject}.Invoke() will also create a mutable Collection data type that will expose similar available methods as ArrayList.
  3. Manually filter the Array’s elements against a certain values, then reassign that result to itself. The below example works on arrays with unique element values.
# Illustration of workaround #3
$arr=@{}
$arr["from"] = @{}; $arr["to"] = @{}
#$arr["from"] = @("C:\Temp\Tech_Documentation"); $arr["to"]=@("\\FILESERVER01\Docs")
$arr["from"] += "C:\Temp\Test"; $arr["to"]+="\\FILESERVER01\Test"
$arr["from"] += "C:\InvalidDirectory"; $arr["to"]+="\\FILESERVER01\Void"

function sanitizeObject{
param($GLOBAL:object=$arr)
$objectLength=$object.from.count

for ($i=0;$i -lt $objectLength; $i++){
$from=$object.from[$i]
$to=$object.to[$i]
#"Checking $from and $to ..."
if(!(test-path $from) -OR !(test-path $to) ){
# Remove row if any path doesn't resolve. Overcome limitation of Powershell's immutable array "fixed size"
#"Removing $($object.from[$i]) and $($object.to[$i]) ..."
$object.from = $object.from|?{$_ -ne $from}
$object.to = $object.to|?{$_ -ne $to}

# Decrement object length pointer value to reflect new length and readjust index number
$objectLength--;
$i--;
}
}
return $object
}

$arr=sanitizeObject -object $arr

PowerShell: How To Bypass Double Hop Problems

# This is a working example of hoping without delegation. Fresh creds can be passed in a nested -ScriptBlock

$hop1="SERVER01"
$hop2="SERVER02"
$domainAdmin="someAdmin"

$cred = Get-Credential "$env:USERDOMAIN`\$domainAdmin"

Invoke-Command -ComputerName $hop1 -Credential $cred -ScriptBlock{
param($hop2)
"Currently at hop: $ENV:computername.";

Invoke-Command -ComputerName $hop2 -Credential $Using:cred -ScriptBlock {
"Reached hop: $ENV:computername.";
}

} -Args $hop2

PowerShell: Reset Failover Clustering Quorum

# Adding Prerequisite Microsoft Cluster
Function installFailoverClustersModule{
if (!(get-module -Name "FailoverClusters") ){
#Install-WindowsFeature Failover-Clustering | out-null;
Install-WindowsFeature RSAT-Clustering-MGMT | out-null;
Install-WindowsFeature RSAT-Clustering-PowerShell | out-null;
Import-Module FailoverClusters | out-null;
}
}
installFailoverClustersModule;

# Fix quorum, Force a cluster to start by seizing the forum
$nodeName="SQL01"
Import-Module FailoverClusters
Stop-ClusterNode –Name $nodeName
Start-ClusterNode –Name $nodeName -FixQuorum
(Get-ClusterNode $nodeName).NodeWeight = 1 #set its weigh to 1

PowerShell: Create Daily VSS Snapshot of Volumes on Local Windows Machine

<# Daily-VSS-Snapshots.ps1

Functions:
1. Dynamically detect all volumes on local machine
2. Dynamically take snapshots of each volume and mount them onto C:\Snapshots\$hostname\$volumeLabel\$snapshotTimeStamp
3. Copy symlinks of those local VSS client access links to a network share in this format (\\Snapshots\FileServerClusters\$clusterName\$hostname\$volumeLabel\$timeStamp)
4. Remove all previous snapshots that are older than retention period

Limitations:
1. VSS must be available on the host Windows machine
2. PowerShell version 3.0 or higher is assumed

Technical considerations:
1. This program should not be used to replace an Enterprise Grade servers backup system (e.g. Veeam, Veritas, Rubik).
2. WARNING: locally mounted snapshots will NOT be recoverable when the server itself becomes inoperable.

Quick Notes:
# command to delete all VSS Snapshots on the local system
vssadmin delete shadows /all /Quiet
#>

# Init variable to store local volume labels: this returns all fixed local volumes, excluding CD Roms, USB drives, and C:\
#$cdRomDrives=Get-CimInstance Win32_LogicalDisk | ?{ $_.DriveType -eq 5} | select DeviceID
$driveLettersExclusion="[BC]\:"
#$localVolumes=(Get-CimInstance Win32_LogicalDisk | ?{ $_.DriveType -eq 3}).DeviceID|Where{$_ -notmatch $driveLettersExclusion} #requires PowerShell version 3.0+
$localVolumes=Get-WmiObject Win32_LogicalDisk | ?{ $_.DriveType -eq 3}|Where{$_.DeviceID -notmatch $driveLettersExclusion}|Select DeviceID|%{$_.DeviceID} #PowerShell 2.0 compatible

# Set snapshot root directory variable
$localSnapshotDirectory="C:\Snapshots"
$remoteSnapshotDirectory="\\Snapshots\FileServerClusters"

# Set Retention Period
$retentionPeriod=7;

# Set hostname
$hostname=$env:computername

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################

# Adding Prerequisite Microsoft Cluster
Function installFailoverClustersModule{
if (!(get-module -Name "FailoverClusters") ){
Try{
Import-Module FailoverClusters | out-null;
}
catch{
# On error, install the missing module
Install-WindowsFeature RSAT-Clustering-MGMT | out-null;
Install-WindowsFeature RSAT-Clustering-PowerShell | out-null;
Import-Module FailoverClusters | out-null;
}
}

function enableR2RSymbolicLinks{
<# Preemptively resolve this error
\\FILESHERVER\SomeShare is not accessible. You might not have permission to use this network resource. Contact the administrator of this server to find out if you have access permissions.
The symbolic link cannot be followed because its type is disabled.
#>

# Enable Remote to remote symlink following if it's not already set
$r2rEnabled=fsutil behavior query SymlinkEvaluation | select-string -Pattern "Remote to remote symbolic links are enabled."
$r2lEnabled=fsutil behavior query SymlinkEvaluation | select-string -Pattern "Remote to local symbolic links are enabled."
if (!($r2rEnabled) -or !($r2lEnabled)){
write-host "`Symbolic links following is disabled.`nEnabling this feature..."
fsutil behavior set SymlinkEvaluation R2R:1;
fsutil behavior set SymlinkEvaluation R2L:1
write-host "`nThis is now the system's settings after the change:"
fsutil behavior query SymlinkEvaluation
}
}


function checkDiskFree{
[cmdletbinding()]
param(
[string]$volume="C:\",
[string]$targetNode="localhost"
)

<#
Excerpt from http://technet.microsoft.com/en-us/library/ee692290(WS.10).aspx:

"For volumes less than 500 megabytes, the minimum is 50 megabytes of free space.
For volumes more than 500 megabytes, the minimum is 320 megabytes of free space.
It is recommended that least 1 gigabyte of free disk space on each volume if the volume size is more than 1 gigabyte."

#>

# Import variables
$thisNode=$targetNode

# Fix targetVolume input if it's missing the suffix
if ($volume -like "*\"){$thisVolume=$volume.Substring(0,$volume.Length-1)}else{$thisVolume=$volume}

# Obtain disk information
$diskObject = Get-WmiObject Win32_LogicalDisk -ComputerName $thisNode -Filter "DeviceID='$thisVolume'"
$diskFree=[Math]::Round($diskObject.FreeSpace / 1MB)
$diskSize=[Math]::Round($diskObject.Size / 1MB)

switch ($diskSize){
{$diskSize -ge 1024} {if ($diskFree -gt 1024){$feasible=$True;}else{$feasible=$False;};;break;}
{$diskSize -ge 500} {if ($diskFree -gt 320){$feasible=$True;}else{$feasible=$False;};;break;}
{$diskSize -lt 500} {if ($diskFree -gt 50){$feasible=$True;}else{$feasible=$False;};break;}
}

return $feasible
}

function removeSnapshots{
[cmdletbinding()]
param(
[int]$daysOlderThan=365
)

# Remove links older than X days
function removeLinks{
[cmdletbinding()]
param(
[string]$snapshotRootDirectory="C:\Snapshots",
[int]$olderThanXDays=365
)
$allSymlinks=Get-ChildItem $snapshotRootDirectory -Recurse -Depth 3 -ErrorAction SilentlyContinue|Where-Object {($_.Attributes -match "ReparsePoint")}

foreach ($link in $allSymLinks){
$creationTime=$link.CreationTime
$thisSymlink=$link.FullName
$removeSymlink=$creationTime -lt (Get-Date).AddDays(-$olderThanXDays)

# Remove symlink if condition is true
if ($removeSymlink){
(Get-Item $thisSymlink).Delete()
"Shadow link $thisSymlink with creation time of $creationTime has been removed."
}else{
"Shadow link $thisSymlink with creation time of $creationTime has NOT been removed."
}
}
}

# Remove old snapshots
function deleteOldSnapshots{
[cmdletbinding()]
param(
[int]$olderThanXDays=365
)
$allSnapshots=Get-WmiObject Win32_Shadowcopy

$allSnapshots | ForEach-Object {
$snapshotDate = $_.InstallDate
$snapshotID = $_.ID
$snapshotDateTimeValue = [management.managementDateTimeConverter]::ToDateTime($snapshotDate)
$thisClientAccessibleValue = $_.ClientAccessible
$currentDate = Get-Date
$timeSpan = New-TimeSpan $snapshotDateTimeValue $currentDate
$days = $timeSpan.Days

If ($days -ge $olderThanXDays -and $thisClientAccessibleValue -eq "True") {
$_.Delete()
"$snapshotID with date stamp of $snapshotDate has been deleted."
} else{
"$snapshotID with date stamp of $snapshotDate has NOT been deleted."
}
}
}

removeLinks -snapshotRootDirectory $remoteSnapshotPath -olderThanXDays $daysOlderThan
removeLinks -snapshotRootDirectory $localSnapshotPath -olderThanXDays $daysOlderThan
deleteOldSnapshots -olderThanXDays $daysOlderThan
}

function createNewSnapshot{
[cmdletbinding()]
param(
[string]$targetVolume="C:\",
[string]$label=$volumeLabel,
[string]$localShapshot="$localSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')",
[string]$remoteSnapshot="$remoteSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')"
)

# Create snapshot directory
New-Item -ItemType Directory -Force -Path "$localSnapshotPath\$hostname\$volumeLabel\" | Out-Null;
New-Item -ItemType Directory -Force -Path "$remoteSnapshotPath\$hostname\$volumeLabel\" | Out-Null;

# Fix targetVolume input if it's missing the suffix
if (!($targetVolume -like "*\")){$targetVolume+="\"}

# Create the 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 + "\";

# Make links to this snapshot
#cd $snapshotPath | out-null
cmd /c mklink /d $localShapshot $thisShadowPath;
cmd /c mklink /d $remoteSnapshot $localShapshot;
#cmd /c mklink /d $snapshotPath $thisShadowPath;
<# PowerShell version 5.0 required
+-----------------------+-----------------------------------------------------------+
| mklink syntax | Powershell equivalent |
+-----------------------+-----------------------------------------------------------+
| mklink Link Target | New-Item -ItemType SymbolicLink -Name Link -Target Target |
| mklink /D Link Target | New-Item -ItemType SymbolicLink -Name Link -Target Target |
| mklink /H Link Target | New-Item -ItemType HardLink -Name Link -Target Target |
| mklink /J Link Target | New-Item -ItemType Junction -Name Link -Target Target |
+-----------------------+-----------------------------------------------------------+

SymbolicLink (modern) supports UNC paths, while Junction (older) does not.
#>
"Snapshot of $targetVolume has been made and it's accessible at this path: $remoteSnapshot"

# Export variables that this specific snapshot can be targeted and removed
#$GLOBAL:shadow=$thisShadow;
#$GLOBAL:snapshot=$thisSnapshot;
}

function proceed{
installFailoverClustersModule;
$clusterName=(get-cluster).name
$GLOBAL:localSnapshotPath="$localSnapshotDirectory\$clusterName"
$GLOBAL:remoteSnapshotPath="$remoteSnapshotDirectory\$clusterName"

New-Item -ItemType Directory -Force -Path $remoteSnapshotPath | Out-Null;
New-Item -ItemType Directory -Force -Path $localSnapshotPath | Out-Null;

enableR2RSymbolicLinks;
if ((get-item $localSnapshotPath) -and (get-item $remoteSnapshotPath) -and ($localVolumes)){
$localVolumes|%{
$GLOBAL:volumeLetter="$_"[0];
$GLOBAL:volumelabel="Volume_$volumeLetter`_$((get-volume -DriveLetter $volumeLetter).FileSystemLabel)";
if (!($volumeLabel)){$volumelabel="Volume_$volumeLetter"};
$snapshotLink="$localSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')";
$snapshotRemoteCopy="$remoteSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')"

if(checkDiskFree -volume $_){
createNewSnapShot -targetVolume $_ -label $volumeLabel -localShapshot $snapshotLink -remoteSnapshot $snapshotRemoteCopy;
}else{"Volume $_ does NOT have sufficient disk space available for taking snapshots."}
}
removeSnapshots -daysOlderThan $retentionPeriod
}else{"Program aborted due to missing items."}
}

proceed;

PowerShell: Check Servers on Domain to Locate A Domain Account Being Set to Run Services and Scheduled Tasks

# Obtain the name of the default domain Administrator account (this account is expected to be disabled)
$domain=(net config workstation) -match 'Workstation domain\s+\S+$' -replace '.+?(\S+)$','$1';
$defaultDomainAdmin="$domain\Administrator"

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

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

Function checkServiceAccount($searchAccount,$sherver){

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

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

# Process the CSV result into PowerShell. Filter entries that are not labeled as "TaskName" and by "Run as User" field
$result = $tasksAsCSV | ConvertFrom-Csv | Where { $_.TaskName -ne "TaskName" -and $_."Run As User" -eq $runasUser}

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

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

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

$servers | %{checkServiceAccount $defaultDomainAdmin $_}

PowerShell: Windows Servers Discovery

<# Servers-Discovery.ps1
Version: 0.02

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

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

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

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

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

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

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

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

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

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

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

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

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

Function checkServiceAccount($searchAccount,$sherver){

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

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

# Process the CSV result into PowerShell. Filter entries that are not labeled as "TaskName" and by "Run as User" field
$result = $tasksAsCSV | ConvertFrom-Csv | Where { $_.TaskName -ne "TaskName" -and $_."Run As User" -eq $runasUser}

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

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

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

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

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

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

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

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

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

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

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

return $result;
}

function checkRDP($remoteSherver){
function localfunc{
$rdpAuth=(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").UserAuthenticationRequired
$encryptionLevel=(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").MinEncryptionLevel
switch ($encryptionLevel){
1 {$compliant="Low";}
2 {$compliant="Client Compatible";}
3 {$compliant="High";}
4 {$compliant="FIPS Compliant";}
}
if($rdpAuth){return "RDP Network Authentication Requirement: passed!`nRDP Encryption Level: $compliant";}else{return "RDP Network Authentication Requirement: Fail";}
}

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

return $result;
}

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

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

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

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

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

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

return $result;
}

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

# Checking IE
$ieKeys=@(
@("CVE-2017-829 (32-Bit)","HKLM:SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX"),
@("CVE-2017-8529 (64-bit)","HKLM:SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX"),
@("ASLR Hardening Setting for IE (32-Bit)","HKLM:SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING"),
@("ASLR Hardening Setting for IE (64-Bit)","HKLM:SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING")
)
$result+="Internet Explorer: "
foreach ($ieKey in $ieKeys){
try{
$value=(Get-ItemProperty -Path $ieKey[1] -Name "iexplore.exe" -ErrorAction SilentlyContinue).'iexplore.exe';
}
catch{
$value=0;
continue;
}
$ieResult=if($value){"Pass"}else{"Fail";}
$ieKey[0] + ": " + $ieResult
}
$result+="$ieResult";

# Checking Memory Management
$memKeys=@(
@("CVE-2017-5715","HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management","FeatureSettingsOverride","0"),
@("CVE-2017-5715","HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management","FeatureSettingsOverrideMask","3"),
@("CVE-2017-5753-54","HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization","MinVmVersionForCpuBasedMitigations","1.0")
)
$result+="`nMemory Management Registry Keys: "
foreach ($memKey in $memKeys){
$value=(Get-ItemProperty -Path $memKey[1] -Name $memKey[2] -ErrorAction SilentlyContinue).[string]($memKey[2])
$memResult=if($value -eq $memKey[3]){"Pass"}else{"Fail";}
$memKey[0]+ ": " + $memResult;
}
$result+="$memResult";

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

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

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

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

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

return $result;
}

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

return $otherVulnerabilities;
}

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

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

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

Function Confirm-AzureVM {

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

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

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

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

yield return nic;
}
}

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

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

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

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

NativeMethods.DhcpRequestFlags flags = NativeMethods.DhcpRequestFlags.DHCPCAPI_REQUEST_SYNCHRONOUS;

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

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

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

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

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

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

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

#region Native Methods
}

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

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

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

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

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

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

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

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

Add-Type -TypeDefinition $source

$detected = $False

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

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

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

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

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

} -ArgumentList ${function:Confirm-AzureVM}

return $result;
}

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

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

return $success;
}

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

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

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

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

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

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

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

end {
return $result
}
}

function systemsDiscovery{

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

systemsDiscovery;

Changing SMB Caching on Windows

# To turn OFF caching
$rolesWithNoCaching="SHERVER01","SHERVER02"
$rolesWithNoCaching | %{$shares=Get-SMBShare -scopename "$_"; foreach ($share in $shares){if(!($share.Name -like "*$")){"Turning caching OFF for share: $share.Name..."; Set-SMBShare -Name $share.Name -CachingMode None -Confirm:$False;}};}

# To turn ON caching
$rolesWithCaching="SHERVER03","SHERVER04"
$rolesWithCaching | %{$shares=Get-SMBShare -scopename "$_"; foreach ($share in $shares){if(!($share.Name -like "*$")){"Turning caching ON for share: $share.Name..."; Set-SMBShare -Name $share.Name -CachingMode Manual -Confirm:$False;}};}

# Changing caching mode for a single share
$shareName="SHARE007"
Set-SMBShare -Name $shareName -CachingMode None -Confirm:$False;
Set-SMBShare -Name $shareName -CachingMode Manual -Confirm:$False;

# Legend:
-- None: Prevents users from storing documents and programs offline.
-- Manual: Allows users to identify the documents and programs that they want to store offline. Equivalent to "Allow Caching of Shares"
-- Programs: Automatically stores documents and programs offline.
-- Documents: Automatically stores documents offline.
-- BranchCache: Enables BranchCache and manual caching of documents on the
# Changing cache settings for Windows 2008R2 or older:
# Set DC Name
$dcSherver="DC01"

# All Files and Programs that users open from the shared folder are automatically available offline. Also, Optimized for performance is enabled.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:Programs")

# Only the files and programs that users specify are available offline.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:Manual")

# All Files and Programs that users open from the shared folder are automatically available offline. Optimized for performance is not checked.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:Documents")

# No files or programs from the shared folder are available offline.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:None")

PowerShell: List All IPs Used by Cluster

Snippet:

# Obtain IPs of resources in cluster
function getResourceIPs {
$resourceIPs=Get-Cluster | Get-ClusterResource | ?{$_.ResourceType -like "IP Address"}
foreach ($resource in $resourceIPs) {
$resource | select OwnerGroup,
@{Name="Address"; Expression={$_ | Get-ClusterParameter -Name Address | select -ExpandProperty Value}},
@{Name="SubnetMask"; Expression={$_ | Get-ClusterParameter -Name SubnetMask | select -ExpandProperty Value}}
}
}

Sample output:

OwnerGroup      Address       SubnetMask
---------- ------- ----------
Cluster Group 192.168.500.112 255.255.255.0
COCOSHOME40-41-n 192.168.500.10 255.255.255.0
COCOECAPPS01-n 192.168.500.11 255.255.255.0
COCOECDIST01-n 192.168.500.12 255.255.255.0
COCOECELRNFS01-n 192.168.500.13 255.255.255.0
COCOECIMAGES01-n 192.168.500.14 255.255.255.0
COCOECLMSFS01-n 192.168.500.16 255.255.255.0
COCOECSHARE01-n 192.168.500.17 255.255.255.0
COCOECZANGLEBK-n 192.168.500.18 255.255.255.0
COCOFSHOME01-n 192.168.500.19 255.255.255.0
COCOFSSHARE01-n 192.168.500.20 255.255.255.0
COCOSHOME01-n 192.168.500.3 255.255.255.0
COCOSHOME30-31-n 192.168.500.5 255.255.255.0
COCOSHOME32-33-n 192.168.500.6 255.255.255.0
COCOSHOME34-35-n 192.168.500.7 255.255.255.0
COCOSHOME36-37-n 192.168.500.8 255.255.255.0
COCOSHOME38-39-n 192.168.500.9 255.255.255.0

PowerShell: Setup Windows Scheduled Tasks

# Scheduled-tasks-remote.ps1
# Purpose: to add a schedule task onto remote Windows system(s)
# Requires: PowerShell 3.0 or higher at the Jump box. Powershell 1.0 or higher at the targets.

# Scheduled task variables
$servers="SHERVER01","SHERVER02","SHERVER03"
$scriptFile="\\snapshots\FileServerClusters\File_Copy_Script_V0.17.ps1"
$description="Daily filecopying operations."
$taskName="Daily_Files_Copy"
[DateTime]$timeToStartTasks = '5:00pm'

# Credentials
$user=Read-Host -Prompt "Input $env:USERDOMAIN administrator's username"
$plainTextPassword=Read-Host -Prompt 'Input the password'
$password=ConvertTo-SecureString -String $plainTextPassword -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user,$password

function isUnc($path){
$GLOBAL:uncServer=$scriptFile | select-string -pattern "(?<=\\\\)(.*?)(?=\\)" | Select -ExpandProperty Matches | Select -ExpandProperty Value
if ($uncServer){return $True}else{return $False}
}

function isDomainAdmin($account){
if (!(get-module activedirectory)){Install-WindowsFeature RSAT-AD-PowerShell -Confirm:$false}
if((Get-ADUser $user -Properties MemberOf).MemberOf -match 'Domain Admins'){return $True;}else{return $false;}
}

function isValidCred($u,$p){
# Get current domain using logged-on user's credentials
$domain = "LDAP://" + ([ADSI]"").distinguishedName
$domainCred = New-Object System.DirectoryServices.DirectoryEntry($domain,$u,$p)
if ($domainCred){return $True}else{return $False}
}

if ((isDomainAdmin $user)-AND (isValidCred $user $password)){
if (isUnc $scriptFile){
foreach ($computer in $servers){
"Processing $computer...";
Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock{
param($scriptFile,$taskName,$description,$user,$password,$uncServer,$timeToStart)
<# Debug
"Running with these variables:`n"
"`nUserID: $user"
"`nPassword: $password"
"`nScript: $scriptFile"
"`nTask name: $taskName"
"`nDescription: $description"
"`nUNC server: $uncServer"
#>
$username="$env:USERDOMAIN`\$user"

# Execute this sequence if the detected PowerShell version is 3.0 or greater
$powershellVersion=$PSVersionTable.PSVersion.Major
if($powershellVersion -ge 3){
"Powershell version $powershellVersion is detected. Now running commands basing on features available in this release...";
$settingsCommand = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit 0
$callPowerShell = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $scriptFile"
$dailyTrigger = New-ScheduledTaskTrigger -Daily -At $timeToStart

# Unrestrict this Domain Administrator from security prompts
Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted -Force

# Unblock file & Ensure that script exists
<# Overcome error caused by double hop issue:
Cannot find path '\\snapshots\FileServerClusters\Daily-VSS-Snapshot.ps1' because it does not exist.
+ CategoryInfo : ObjectNotFound: (\\snapshots\Fil...SS-Snapshot.ps1:String) [Unblock-File], ItemNotFoundException
+ FullyQualifiedErrorId : FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand
+ PSComputerName : SHERVER007

1. Run scheduled task as: New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $scriptFile"
2. Unblock-File -Path $scriptFile
#>
if ((Invoke-Command -computername $uncServer -Credential $Using:cred -ScriptBlock{Unblock-File -Path $Args[0];Test-Path $Args[0] -ErrorAction SilentlyContinue}-Args $scriptFile) -eq $False) {"Errors locating $scriptFile... Skipping";break;}

# Unregister the Scheduled task if it already exists
Get-ScheduledTask $taskName -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false;

# Create new scheduled task
Register-ScheduledTask -Action $callPowerShell -Trigger $dailyTrigger -TaskName $taskName -Description $description -User $username -Password $password -Settings $settingsCommand -RunLevel Highest;
}else{
"Powershell version $powershellVersion is detected. Setting scheduled task as $username with that release...";
$TaskDescription = $description
$TaskCommand = "powershell.exe"
$TaskScript = $scriptFile
$TaskArg = "-WindowStyle Hidden -NonInteractive -Executionpolicy unrestricted -file $TaskScript"
#$TaskStartTime = [datetime]::Now.AddMinutes(60)
$TaskStartTime = $timeToStart
$service = new-object -ComObject("Schedule.Service")
$service.Connect()
$rootFolder = $service.GetFolder("\")
$TaskDefinition = $service.NewTask(0)
$TaskDefinition.RegistrationInfo.Description = $TaskDescription
$TaskDefinition.RegistrationInfo.Author = $taskRunAsuser
$TaskDefinition.Settings.Enabled = $true
$TaskDefinition.Settings.AllowDemandStart = $true
$TaskDefinition.Settings.ExecutionTimeLimit = "PT0S"
$TaskDefinition.Principal.RunLevel = 1
$triggers = $TaskDefinition.Triggers
$trigger = $triggers.Create(2)
$trigger.StartBoundary = $TaskStartTime.ToString("yyyy-MM-dd'T'HH:mm:ss")
$trigger.Enabled = $true
$Action = $TaskDefinition.Actions.Create(0)
$action.Path = $TaskCommand
$action.Arguments = $TaskArg
$rootFolder.RegisterTaskDefinition($TaskName,$TaskDefinition,6,$username,$password,1)
}
} -ArgumentList $scriptFile,$taskName,$description,$user,$plainTextPassword,$uncServer,$timeToStartTasks

"$computer scheduled task is now set."
#pause;
$timeToStartTasks = $timeToStartTasks.AddMinutes(5)
}
}
}else{
"Need to run this program with a valid Domain Administrator account."
}
# scheduled-task.ps1
# Purpose: to add a schedule onto a local system
# Requires: PowerShell 3.0 or higher

$scriptFile='C:\scripts\filecopy.ps1'
$sourceScriptFile="\\SHERVER007\c$\Software\File_Copy_Script_V0.16.ps1"
$callPowerShell = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $scriptFile"
$dailyTrigger = New-ScheduledTaskTrigger -Daily -At 5pm
$user= "$env:USERDOMAIN\$username"
$password= 'PASSWORT'
$description="file copying operations"
$taskName="Files_Copy"
$settingsCommand = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit 0

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################

# Ensure that script exists at destination
New-Item -ItemType Directory -Force -Path "C:\scripts";
if (-NOT (Test-Path $scriptFile)) {Copy-Item $sourceScriptFile -Destination $scriptFile -Force}

# Unregister the Scheduled task if it already exists
Get-ScheduledTask $taskName -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false ;

# Create new scheduled task
Register-ScheduledTask -Action $callPowerShell -Trigger $dailyTrigger -TaskName $taskName -Description $description -User $user -Password $password -Settings $settingsCommand -RunLevel Highest

# Call the GUI version for double checking purposes
#taskschd.msc
#pause;

PowerShell: Scan a Subnet for Used and Unused IPs

Script:
# Specify the servers subnet to scan for available IPs
$serversCidrBlock="192.168.10.0/24"

function scanForAvailableIPs{
# This Get-IPrange function has been obtained at https://gallery.technet.microsoft.com/scriptcenter/List-the-IP-addresses-in-a-60c5bb6b
# Snippet Author: BarryCWT
function Get-IPrange{
<#
.SYNOPSIS
Get the IP addresses in a range
.EXAMPLE
Get-IPrange -start 192.168.8.2 -end 192.168.8.20
.EXAMPLE
Get-IPrange -ip 192.168.8.2 -mask 255.255.255.0
.EXAMPLE
Get-IPrange -ip 192.168.8.3 -cidr 24
#>

param
(
[string]$start,
[string]$end,
[string]$ip,
[string]$mask,
[int]$cidr
)

function IP-toINT64 () {
param ($ip)

$octets = $ip.split(".")
return [int64]([int64]$octets[0]*16777216 +[int64]$octets[1]*65536 +[int64]$octets[2]*256 +[int64]$octets[3])
}

function INT64-toIP() {
param ([int64]$int)

return (([math]::truncate($int/16777216)).tostring()+"."+([math]::truncate(($int%16777216)/65536)).tostring()+"."+([math]::truncate(($int%65536)/256)).tostring()+"."+([math]::truncate($int%256)).tostring() )
}

if ($ip) {$ipaddr = [Net.IPAddress]::Parse($ip)}
if ($cidr) {$maskaddr = [Net.IPAddress]::Parse((INT64-toIP -int ([convert]::ToInt64(("1"*$cidr+"0"*(32-$cidr)),2)))) }
if ($mask) {$maskaddr = [Net.IPAddress]::Parse($mask)}
if ($ip) {$networkaddr = new-object net.ipaddress ($maskaddr.address -band $ipaddr.address)}
if ($ip) {$broadcastaddr = new-object net.ipaddress (([system.net.ipaddress]::parse("255.255.255.255").address -bxor $maskaddr.address -bor $networkaddr.address))}

if ($ip) {
$startaddr = IP-toINT64 -ip $networkaddr.ipaddresstostring
$endaddr = IP-toINT64 -ip $broadcastaddr.ipaddresstostring
} else {
$startaddr = IP-toINT64 -ip $start
$endaddr = IP-toINT64 -ip $end
}


for ($i = $startaddr; $i -le $endaddr; $i++)
{
INT64-toIP -int $i
}

}

# Regex values
$regexIP = [regex] "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
$regexCidr=[regex] "\/(.*)"
$regexFourthOctetValue=[regex] ".+\..+\..+\.(.+)"

# Value Extractions
$ip=$regexIP.Matches($serversCidrBlock).Value
$cidr=$regexCidr.Matches($serversCidrBlock).Groups[1].Value
$rawIps=Get-IPrange -ip $ip -cidr $cidr
[System.Collections.ArrayList]$allIPs=$rawIps

# Remove fourth octet values matching 0,1, and 255
if($regexFourthOctetValue.Matches($allIPs[0]).Groups[1].Value -eq 0){$first, $rest= $allIPs; $allIPs=$rest;}
if($regexFourthOctetValue.Matches($allIPs[0]).Groups[1].Value -eq 1){$first, $rest= $allIPs; $allIPs=$rest;}
if($regexFourthOctetValue.Matches($allIPs[$allIPs.count-1]).Groups[1].Value -eq 255){$allIPs.RemoveAt($allIPs.count-1)}

# Display sweep scanning output
#$allIPs | ForEach-Object {if(!(Get-WmiObject Win32_PingStatus -Filter "Address='$_' and Timeout=200 and ResolveAddressNames='true' and StatusCode=0" | select ProtocolAddress*)){$_}}

# Collect unpingable IPs
"Collecting available IPs. Please wait awhile..."
$GLOBAL:availableIPs=$allIPs|%{if(!(Get-WmiObject Win32_PingStatus -Filter "Address='$_' and Timeout=200 and ResolveAddressNames='true' and StatusCode=0"|select ProtocolAddress*)){write-host "$_ is available.";$_}}

# Return the Pingable IPs
$GLOBAL:unavailableIPs=Compare-Object $rawIps $availableIPs -PassThru
}

scanForAvailableIPs;
"`r`nAvailable IPs:`r`n------------------------------------------------`r"
$availableIPs;

"`r`nUnavailable IPs:`r`n------------------------------------------------`r"
$unavailableIPs;
Sample Output:
Available IP:
------------------------------------------------

192.168.10.2
192.168.10.3
192.168.10.4
192.168.10.5
-- Omitted for brevity --
192.168.10.250
192.168.10.251
192.168.10.252
192.168.10.253
192.168.10.254


Unavailable IP:
------------------------------------------------

192.168.10.51
192.168.10.52
192.168.10.102
192.168.10.103

Some Useful Windows Commands to Troubleshoot Networking on Windoze

# Check this computer's trust relationship to its domain controllers
$domainName="intranet.kimconnect.com"
PS C:\Windows\system32> nltest /SC_QUERY:$domainName
Flags: 0
Trusted DC Name
Trusted DC Connection Status Status = 1311 0x51f ERROR_NO_LOGON_SERVERS
The command completed successfully

# Reset computer account password
$domainController="DC01.intranet.kimconnect.com"
$domainAdmin="domainAdmin"
netdom.exe resetpwd /s:$domainController /ud:$domainAdmin /pd:*

Output:
PS C:\Windows\system32> netdom.exe resetpwd /s:$domainController /ud:$domainAdmin /pd:*
Type the password associated with the domain user:
The machine account password for the local machine could not be reset.
The network path was not found.
The command failed to complete successfully.

# Check network interfaces
PS C:\Windows\system32> get-netadapter *
Name InterfaceDescription ifIndex Status MacAddress LinkSpeed
---- -------------------- ------- ------ ---------- ---------
DEV Cisco VIC Ethernet Interface #4 15 Up 00-25-B5 10 Gbps
iSCSI-A Cisco VIC Ethernet Interface #3 14 Up 00-25-B5 10 Gbps
CLUSTER Cisco VIC Ethernet Interface #2 8 Up 00-25-B5 10 Gbps
iSCSI-B Cisco VIC Ethernet Interface 2 Up 00-25-B5 10 Gbps

# Restart a NIC
PS C:\Windows\system32> get-netadapter -Name "PROD" | Restart-NetAdapter

# Overload a NIC with additional IP address(es)
$availableIP="x.x.x.x"
$cidrMask=24
$adapterName="DEV"
New-NetIPAddress –IPAddress $availableIP –PrefixLength $cidrMask –InterfaceAlias $adapterName –SkipAsSource $True

PS C:\Windows\system32> New-NetIPAddress –IPAddress $availableIP –PrefixLength $cidrMask –InterfaceAlias $adapterName –SkipAsSource $True
IPAddress : x.x.x.x
InterfaceIndex : 15
InterfaceAlias : PROD
AddressFamily : IPv4
Type : Unicast
PrefixLength : 24
PrefixOrigin : Manual
SuffixOrigin : Manual
AddressState : Tentative
ValidLifetime : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource : True
PolicyStore : ActiveStore

# Attempt to ping DNS1 from the new IP - failure means no ICMP outbound allowed
PS C:\Windows\system32> ping 1.1.1.1 -i x.x.x.x
Pinging 1.1.1.1 with 32 bytes of data:
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Ping statistics for 1.1.1.1:
Packets: Sent = 4, Received = 0, Lost = 4 (100% loss)

# Set static route. Warning: don't do this unless you really know routing and switching!
$subnet="1.1.1.0"
$cidrMask=24
$ifIndex=15
$nextHop="1.1.1.1"
New-NetRoute -DestinationPrefix "$subnet`/$cidrMask" -InterfaceIndex $ifIndex -NextHop $nextHop
Get-NetRoute

PowerShell: Quickly Start Services that were Set to Auto

Quick Liners:
# Command to check
Get-WmiObject -ClassName Win32_Service -Filter "StartMode='Auto' AND State<>'Running'" | Format-Table -Auto DisplayName,Name,StartMode,State

# Command to start those non-running services
Get-WmiObject -ClassName Win32_Service -Filter "StartMode='Auto' AND State<>'Running'" | Start-Service
Sample Output:
[sherver007.something.loco]: PS C:\Users\176233\Documents> Get-WmiObject -ClassName Win32_Service -Filter "StartMode='Auto' AND State<>'Running'" | Format-Table -Auto DisplayName,Name,StartMode,State

DisplayName Name StartMode State
----------- ---- --------- -----
Downloaded Maps Manager MapsBroker Auto Stopped
Remote Registry RemoteRegistry Auto Stopped
Software Protection sppsvc Auto Stopped
Windows Biometric Service WbioSrvc Auto Stopped

[sherver007.something.loco]: PS C:\Users\176233\Documents> Get-WmiObject -ClassName Win32_Service -Filter "StartMode='Auto' AND State<>'Running'" | Start-Service
# output = silent

[sherver007.something.loco]: PS C:\Users\176233\Documents> Get-WmiObject -ClassName Win32_Service -Filter "StartMode='Auto' AND State<>'Running'" | Format-Table -Auto DisplayName,Name,StartMode,State
# output = de nata

PowerShell: Trigger Azure AD Sync on Remote Server as a Domain Admin

# Check whether a given username matches the list of Domain Admins
function validateDomainAdminMembership{
param (
[string]$username
)
$matchedAdmin=$username -in $domainAdmins
if($matchedAdmin){
Write-Host "$username is a Domain Admin";
return $True;
}else{
Write-Host "$username not a Domain Admin.";
return $False;
}
}

function testCredential{
param (
[string]$username,
[string]$password
)
$plaintextPassword = (New-Object System.Management.Automation.PSCredential 'N/A',$providedPassword).GetNetworkCredential().Password
$domainBindTest = (New-Object System.DirectoryServices.DirectoryEntry($domainObject,$username,$plaintextPassword)).DistinguishedName
if ($domainBindTest){return $True;} else{Return $False;}
}

function obtainDomainAdminCred{
$domainAdmins=(Get-ADGroupMember -Identity "Domain Admins" -Recursive | %{Get-ADUser -Identity $_.distinguishedName} | Where-Object {$_.Enabled -eq $True}).SamAccountName
$global:cred=$False
do {
$providedID=Read-Host -Prompt 'Input a domain admin username'
if (validateDomainAdminMembership $providedID){
$providedPassword = Read-Host -assecurestring "Please enter the password"
#$providedPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
#$providedCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $providedID,$providedPassword
$goodCredential=testCredential -username $providedID -password $providedPassword
if($goodCredential){
"Domain Admin Credential validated!";
$global:cred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $providedID,$providedPassword;
#return $True;
}
else{
"Password doesn't match.";
$global:cred=$False;
#return $False;
}
}else{
"Try again..."
#return $False;
}
} until ($cred)
}

function validateCurrentAccountAsDomainAdmin{
if((whoami /groups) -match 'domain admins'){
"This account is a Domain Admins member";
return $True;
}else{"This account is NOT a Domain Admins member";return $False;}
}

If(!(validateCurrentAccountAsDomainAdmin)){obtainDomainAdminCred;}

# Query Active Directory to obtain the Azure AD Connect servername
$adConnectServers=(Get-ADUser -filter 'name -like "Msol*"' -Properties Description).Description | %{[void]($_ -match "computer\s'(.+)'\sconfigured");$matches[1]}
#if ($adConnectServers.GetType().Name -eq "String"){$serverName = $adConnectServers}else{$serverName = $adConnectServers[0]}

foreach ($servename in $adConnectServers){
"Invoking command on $servename`r`nStart-ADSyncSyncCycle -PolicyType Delta...";

$result=Invoke-Command -computername $serverName -scriptblock {
$value=Start-ADSyncSyncCycle -PolicyType Delta;
return $value.Result;
} -credential $cred

if ($result.Value -like 'Success'){break;}
}

"`r`nDone. Press Enter to close window."
pause;

Sample output:

Invoking command on ADKONNECTSHERVER01
Start-ADSyncSyncCycle -PolicyType Delta...

PSComputerName RunspaceId Result
-------------- ---------- ------
ADKONNECTSHERVER01 031af674-3bca-407c-bfab-d9ffb94123ab Success