PowerShell: Connect to Office 365 Exchange Online

function disconnectExchangeOnline{
<# manually check sessions
PS C:\Windows\system32> Get-PSSession

Id Name ComputerName ComputerType State ConfigurationName Availability
-- ---- ------------ ------------ ----- ----------------- ------------
4 WinRM4 outlook.offi... RemoteMachine Opened Microsoft.Exchange Available
5 WinRM5 outlook.offi... RemoteMachine Opened Microsoft.Exchange Available
#>
$activeExchangeOnlineSessionIds=(Get-PSSession |?{$_.ConfigurationName -eq "Microsoft.Exchange"}).Id
if($activeExchangeOnlineSessionIds){
Remove-PSSession -id $activeExchangeOnlineSessionIds;
write-host "session ID $activeExchangeOnlineSessionIds is disconnected."
}
}

function connectToExchangeOnline{
param(
$credential=(get-credential),
$connectionURI="https://outlook.office365.com/powershell-liveid/"
)
#Disconnect any active sessions prior to initiating a new one
disconnectExchangeOnline;

if (!($activeExchangeOnlineSessionIds)){
#Imports the base MSOnline module
if (!(Get-Module -ListAvailable -Name MSOnline)){Install-Module MSOnline -Confirm:$false -Force;}

$GLOBAL:onlineExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $connectionURI -Credential $credential -Authentication Basic -AllowRedirection
Import-PSSession $onlineExchangeSession -AllowClobber -DisableNameChecking
}else{
write-host "An active session is already available. No new connections were made."
}
}

connectToExchangeOnline;

PowerShell: Get Executable Version and File Location

# Quick method to obtain computernames of all nodes in a cluster and adjoin result with standalone machine names
$standaloneFileServers="komputer1","komputer2"
$sourceClusters="cluster1","cluster2"
$nodes=$sourceClusters|%{get-clusternode -cluster $_}
$computerNames=$nodes.Name+$standaloneFileServers

# function to obtain the version of a particular excecutable
function getExecutableVersion{
param(
[string]$computername,
[string]$executablename="robocopy.exe"
)
$localComputerName=$env:computername
$isLocal=if($computername -match "(localhost|127.0.0.1|$localComputerName)"){$true}else{$false}

function getExeInfo{
param($exeName)
$exeFile=$(try{get-command $exeName -ea SilentlyContinue}catch{}).Definition;
$exeInfo=$(try{get-item $exeFile -ea SilentlyContinue}catch{}).versionInfo;
$osName=(Get-WmiObject -class Win32_OperatingSystem).Caption;
$exeInfo | Add-Member -MemberType NoteProperty -Name "osName" -Value $osName
return $exeInfo;
}

if ($isLocal){
$exeInfo=getExeInfo -exeName $executablename;
}else{
$exeInfo=invoke-command -computername $computername -scriptblock{
param($importedFunc,$executablename)
[ScriptBlock]::Create($importedFunc).Invoke($executablename);
} -args ${function:getExeInfo},$executablename
}
if ($exeInfo){
$exeVersion=$exeInfo.ProductVersion;
$exeLocation=$exeInfo.FileName;
$fileVersion=$exeInfo.FileVersion;
$osName=$exeInfo.osName;
return "$computername $osName $executablename $exeVersion$(if($exeVersion -ne $fileVersion){" $fileVersion "})$exeLocation";
}else{
write-host "$executablename is not in the environmental paths of $computername";
}
}

$computerNames|getExecutableVersion -computerName $_ -executablename robocopy.exe

PowerShell: Add System Backup Privileges

function addSystemPrivilege{
param(
[String[]]$privileges=@("SeBackupPrivilege","SeRestorePrivilege")
)

function includeSystemPrivileges{
$win32api = @'
using System;
using System.Runtime.InteropServices;

namespace SystemPrivilege.Win32API
{
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}

[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public UInt32 Attributes;
}

[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
public LUID Luid;
public UInt32 Attributes;
}

public class Privileges
{
public const UInt32 DELETE = 0x00010000;
public const UInt32 READ_CONTROL = 0x00020000;
public const UInt32 WRITE_DAC = 0x00040000;
public const UInt32 WRITE_OWNER = 0x00080000;
public const UInt32 SYNCHRONIZE = 0x00100000;
public const UInt32 STANDARD_RIGHTS_ALL = (
READ_CONTROL |
WRITE_OWNER |
WRITE_DAC |
DELETE |
SYNCHRONIZE
);
public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000u;
public const UInt32 STANDARD_RIGHTS_READ = 0x00020000u;

public const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001u;
public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002u;
public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004u;
public const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000u;

public const UInt32 TOKEN_QUERY = 0x00000008;
public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x00000020;

public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x00000001u;
public const UInt32 TOKEN_DUPLICATE = 0x00000002u;
public const UInt32 TOKEN_IMPERSONATE = 0x00000004u;
public const UInt32 TOKEN_QUERY_SOURCE = 0x00000010u;
public const UInt32 TOKEN_ADJUST_GROUPS = 0x00000040u;
public const UInt32 TOKEN_ADJUST_DEFAULT = 0x00000080u;
public const UInt32 TOKEN_ADJUST_SESSIONID = 0x00000100u;
public const UInt32 TOKEN_READ = (
STANDARD_RIGHTS_READ |
TOKEN_QUERY
);
public const UInt32 TOKEN_ALL_ACCESS = (
STANDARD_RIGHTS_REQUIRED |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID
);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetCurrentProcess();

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetCurrentThread();

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLengthInBytes, IntPtr PreviousStateNull, IntPtr ReturnLengthInBytesNull);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool OpenThreadToken(IntPtr ThreadHandle, UInt32 DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);

[DllImport("ntdll.dll", EntryPoint = "RtlAdjustPrivilege")]
public static extern int RtlAdjustPrivilege(
UInt32 Privilege,
bool Enable,
bool CurrentThread,
ref bool Enabled
);

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);

//
//

private static LUID LookupPrivilege(string privilegeName)
{
LUID privilegeValue = new LUID();

bool res = LookupPrivilegeValue(null, privilegeName, out privilegeValue);

if (!res)
{
throw new Exception("Error: LookupPrivilegeValue()");
}

return privilegeValue;
}

//
//

public static void AdjustPrivilege(string privilegeName, bool enable)
{
IntPtr accessToken = IntPtr.Zero;
bool res = false;

try
{
LUID privilegeValue = LookupPrivilege(privilegeName);

res = OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, out accessToken);

if (!res)
{
res = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out accessToken);

if (!res)
{
throw new Exception("Error: OpenProcessToken()");
}
}

TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES();
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Luid = privilegeValue;

if (enable)
{
tokenPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
}
else
{
tokenPrivileges.Attributes = 0;
}

res = AdjustTokenPrivileges(accessToken, false, ref tokenPrivileges, (uint)System.Runtime.InteropServices.Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero);

if (!res)
{
throw new Exception("Error: AdjustTokenPrivileges()");
}
}

finally
{
if (accessToken != IntPtr.Zero)
{
CloseHandle(accessToken);
accessToken = IntPtr.Zero;
}
}
}
}
}
'@

if ([object]::Equals(('SystemPrivilege.Win32API.Privileges' -as [type]), $null)) {
Add-Type -TypeDefinition $win32api
}
}
includeSystemPrivileges;


$privileges|%{[SystemPrivilege.Win32API.Privileges]::AdjustPrivilege($_, $true)}

# Validation
whoami /priv|?{$_ -match "SeBackupPrivilege|SeRestorePrivilege"}
}
addSystemPrivilege;

PowerShell: Deleting a Single File Safely

# Name your file
$item="\\FILESHERVER007\someweird folder names with long paths\ fmmkklghhbb-yj-yuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy p-hphlfiles\whatup.exe - Shortcut.lnk" #item could be file or folder

# Move such item into a temp directory, then purge that temp folder
$tempDirectory="c:\tempDirectory"
$emptyDirectory="c:\emptyDirectory"
mkdir $emptyDirectory | out-null
mkdir $emptyDirectory | out-null
$filename=split-path $item -Leaf
$foldername=split-path $item -Parent
robocopy $foldername $tempDirectory $filename /MOVE
robocopy $emptyDirectory $tempDirectory /purge

PowerShell: Increase Default Windows 260-Character Paths Limit

# This function increases the default windows 260 characters path length limit to 1024
Function remove260CharsPathLimit{
param(
[string]$computerName=$env:computername
)
# Declare static variables
$registryHive= "SYSTEM\CurrentControlSet\Control\FileSystem"
$keyName = "LongPathsEnabled"
$value= 1

# The legacy command-line method
#$result=REG ADD "\\$computerName\HKLM\$registryHive" /v $keyName /t REG_DWORD /d $value /f
#if($result){"\\$computerName\HKLM\$registryHive\$keyName has been added successfully."}

#The PowerShell Method to set remote registry key
$registryHive="SYSTEM\CurrentControlSet\Control\FileSystem"
$keyName="LongPathsEnabled"
$value=1
$remoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computerName)
$remoteHive = $remoteRegistry.OpenSubKey($registryHive,$True)
$remoteHive.CreateSubKey($keyName)|out-null
$remoteKeyValue = $remoteRegistry.OpenSubKey("$registryHive\$keyName",$True)
$remoteKeyValue.SetValue($keyName,$value)

# Validation
$resultKey=$remoteRegistry.OpenSubKey("$registryHive\$keyName")
if($resultKey.GetValue($keyName) -eq $value){"\\$computerName\HKLM\$registryHive\$keyName has been added successfully."}
}

# This reverses the effects of that previous function
Function restore260CharsPathLimit{
param([string]$computerName=$env:computername)

$registryHive= "SYSTEM\CurrentControlSet\Control\FileSystem"
$keyName = "LongPathsEnabled"
$result=REG DELETE "\\$computerName\HKLM\$registryHive" /v $keyName /f
if($result){"\\$computerName\HKLM\$registryHive\$keyName has been removed successfully."}
}

PowerShell: How to Properly Delete a Msol User Account

# Set the user ObjectID attribute variable
$msolUser="6f2fcfcd-...."

# Move user account to Recycle Bin
Remove-MsolUser -ObjectId $msolUser

# Purge from Recycle Bin
Get-MsolUser -ObjectID $msolUser -ReturnDeletedUsers | Remove-MsolUser -RemoveFromRecycleBin

# Remove from contacts bin
Get-MsolContact -ObjectID $msolUser | FL #Validate that this is a correct account
Get-MsolContact -ObjectID $msolUser | Remove-MsolContact

# Optional: if object is a group instead of a user
Get-MsolGroup -ObjectID $msolUser | Select EmailAdress,ProxyAddresses #Validate that this is the correct account
Get-MsolGroup -ObjectID $msolUser | Remove-MsolGroup

PowerShell: Play with Time

1. Constructor:

$timer=[System.Diagnostics.Stopwatch]::StartNew()

2. Accessing a property

$totalSeconds=$timer.Elapsed.TotalSeconds

3. Output Time (Usage):

$hours=[math]::round($totalSeconds/3600,2)
write-host "It has been $hours hours."

4. Destructor (optional as the system automatically performs this task when session goes out of scope):

$timer.stop()

PowerShell: Accessing the Reflection Assembly Class to Retrieve User Context

Step 1: Accessing Reflection Assembly namespace to call method Load with Windows Account Management as the input object

PS C:\Windows\system32> [reflection.assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement")

GAC Version Location
--- ------- --------
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.DirectoryServices.AccountManagement\v4.0_4.0...

Step 2: Access the UserPrinciple property to return Current user

PS C:\Windows\system32> [System.DirectoryServices.AccountManagement.UserPrincipal]::Current


GivenName :
MiddleName :
Surname :
EmailAddress :
VoiceTelephoneNumber :
EmployeeId :
AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters
Enabled : True
AccountLockoutTime :
LastLogon : 10/27/2019 9:16:26 PM
PermittedWorkstations : {}
PermittedLogonTimes : {255, 255, 255, 255...}
AccountExpirationDate :
SmartcardLogonRequired : False
DelegationPermitted : True
BadLogonCount : 0
HomeDirectory :
HomeDrive :
ScriptPath :
LastPasswordSet : 3/15/2019 4:37:35 AM
LastBadPasswordAttempt :
PasswordNotRequired : True
PasswordNeverExpires : True
UserCannotChangePassword : False
AllowReversiblePasswordEncryption : False
Certificates : {}
Context : System.DirectoryServices.AccountManagement.PrincipalContext
ContextType : Machine
Description :
DisplayName :
SamAccountName : baloo
UserPrincipalName :
Sid : S-1-5-21-3577067864-56025819-3891708608-1002
Guid :
DistinguishedName :
StructuralObjectClass :
Name : baloo

Step 3: View the available methods of current user context

PS C:\Windows\system32> [System.DirectoryServices.AccountManagement.UserPrincipal]::Current | Get-Member


TypeName: System.DirectoryServices.AccountManagement.UserPrincipal

Name MemberType Definition
---- ---------- ----------
ChangePassword Method void ChangePassword(string oldPassword, string newPassword)
Delete Method void Delete()
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object o)
ExpirePasswordNow Method void ExpirePasswordNow()
GetAuthorizationGroups Method System.DirectoryServices.AccountManagement.PrincipalSearchResult[System...
GetGroups Method System.DirectoryServices.AccountManagement.PrincipalSearchResult[System...
GetHashCode Method int GetHashCode()
GetType Method type GetType()
GetUnderlyingObject Method System.Object GetUnderlyingObject()
GetUnderlyingObjectType Method type GetUnderlyingObjectType()
IsAccountLockedOut Method bool IsAccountLockedOut()
IsMemberOf Method bool IsMemberOf(System.DirectoryServices.AccountManagement.GroupPrincip...
RefreshExpiredPassword Method void RefreshExpiredPassword()
Save Method void Save(), void Save(System.DirectoryServices.AccountManagement.Princ...
SetPassword Method void SetPassword(string newPassword)
ToString Method string ToString()
UnlockAccount Method void UnlockAccount()
AccountExpirationDate Property System.Nullable[datetime] AccountExpirationDate {get;set;}
AccountLockoutTime Property System.Nullable[datetime] AccountLockoutTime {get;}
AdvancedSearchFilter Property System.DirectoryServices.AccountManagement.AdvancedFilters AdvancedSear...
AllowReversiblePasswordEncryption Property bool AllowReversiblePasswordEncryption {get;set;}
BadLogonCount Property int BadLogonCount {get;}
Certificates Property System.Security.Cryptography.X509Certificates.X509Certificate2Collectio...
Context Property System.DirectoryServices.AccountManagement.PrincipalContext Context {get;}
ContextType Property System.DirectoryServices.AccountManagement.ContextType ContextType {get;}
DelegationPermitted Property bool DelegationPermitted {get;set;}
Description Property string Description {get;set;}
DisplayName Property string DisplayName {get;set;}
DistinguishedName Property string DistinguishedName {get;}
EmailAddress Property string EmailAddress {get;set;}
EmployeeId Property string EmployeeId {get;set;}
Enabled Property System.Nullable[bool] Enabled {get;set;}
GivenName Property string GivenName {get;set;}
Guid Property System.Nullable[guid] Guid {get;}
HomeDirectory Property string HomeDirectory {get;set;}
HomeDrive Property string HomeDrive {get;set;}
LastBadPasswordAttempt Property System.Nullable[datetime] LastBadPasswordAttempt {get;}
LastLogon Property System.Nullable[datetime] LastLogon {get;}
LastPasswordSet Property System.Nullable[datetime] LastPasswordSet {get;}
MiddleName Property string MiddleName {get;set;}
Name Property string Name {get;set;}
PasswordNeverExpires Property bool PasswordNeverExpires {get;set;}
PasswordNotRequired Property bool PasswordNotRequired {get;set;}
PermittedLogonTimes Property byte[] PermittedLogonTimes {get;set;}
PermittedWorkstations Property System.DirectoryServices.AccountManagement.PrincipalValueCollection[str...
SamAccountName Property string SamAccountName {get;set;}
ScriptPath Property string ScriptPath {get;set;}
Sid Property System.Security.Principal.SecurityIdentifier Sid {get;}
SmartcardLogonRequired Property bool SmartcardLogonRequired {get;set;}
StructuralObjectClass Property string StructuralObjectClass {get;}
Surname Property string Surname {get;set;}
UserCannotChangePassword Property bool UserCannotChangePassword {get;set;}
UserPrincipalName Property string UserPrincipalName {get;set;}
VoiceTelephoneNumber Property string VoiceTelephoneNumber {get;set;}

PowerShell: Detect Antivirus Name on a Windows Machine

function getAntivirusName {  
$wmiQuery = "SELECT * FROM AntiVirusProduct"
$antivirus = Get-WmiObject -Namespace "root\SecurityCenter2" -Query $wmiQuery @psboundparameters -ErrorVariable myError -ErrorAction 'SilentlyContinue'

if($antivirus){
return $antivirus.displayName
}else{
$alternateAntivirusQuery=WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct GET displayName /Format:List|?{$_.trim() -ne ""}|%{$_ -replace "displayName=",""}
if ($alternateAntivirusQuery){
return $alternateAntivirusQuery
}else{
write-host "No antivirus software were detected. Hence, namespace querying errors."
$rawSearch=((get-wmiobject -class "Win32_Process" -namespace "root\cimv2" | where-object {$_.Name.ToLower() -match "antivirus|endpoint|protection|security|defender|msmpeng"}).Name | Out-String).Trim();
if($rawSearch){
return $rawSearch
}else{
return "No antivirus detected."
}
}

}
}
getAntivirusName;

PowerShell: How To Add Additional Sub-Directories to an Existing SMB Share

Business Use-Case:

  1. There’s an existing logon script or Group Policy that maps users toward a particular share on a file server (e.g. “NET USE P:\ \\FILESHERVER01\Public /USER:INTRANET.KIMCONNECT.COM\%USERNAME%”)
  2. The requirement is to add more directories to that existing share without incurring extra storage on existing volume where that share resides
  3. There is a new LUN being dedicated to that file server that is intended to be mounted as a sub-directory at path \\FILESHERVER01\Public\Infrastructure
  4. There are other existing volumes at local paths of T:\Software and M:\Public_Relations, and all other existing shares on this server, that must be accessible from \\FILESHERVER01\Public as sub-folders named Software and Public_Relations, etc.

Technical Implementation:

  • a. There shall be no changes required for the logon script. Hence, no change requests are needed to perform this task.
  • b. The default recommendation is to expand the volume the hosting share \\FILESHERVER01\Public. If that is not feasible, then items (c) and (d) shall apply
  • c. These are the PowerShell commands to mount a new volume as a folder inside \\FILESHERVER01\Public
<# Method 1: Dedicate a whole volume as a sub-directory 
Warning: all data on this new volume will be erased.

#### Set variables ####
$mountPath=""S:\Public\Infrastructure"
$diskIndex=5
#### Only edit above lines ####

$disk = Get-Disk $diskIndex
$disk | Clear-Disk -RemoveData -Confirm:$false
$disk | Initialize-Disk -PartitionStyle MBR
$disk | New-Partition -UseMaximumSize -MbrType IFS
$partition = Get-Partition -DiskNumber $disk.Number
$partition | Format-Volume -FileSystem NTFS -Confirm:$false
New-Item -ItemType Directory -Force -Path $mountPath
$partition | Add-PartitionAccessPath -AccessPath $mountPath

#>
  • d. This PowerShell snippet shall retrieve existing SMB shares on the file server and create “junctions” that are accessible from \\FILESHERVER01\Public
<# Method 2: Create Junction Points to All Shares On File Server 

#### Set variables ############
$shareRoot="S:\Public"
#### Only edit above lines ####

# Gather SMB shares on this file server
$paths=get-smbshare|?{$_.Name -notlike "*$"}|select Name,Path

# Install junction.exe
if (!(get-command junction)){
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
choco install junction -y
}

# Create junctions at SMB Share Root with a given path
function createJunction{
param(
[string]$path,
[string]$smbShareRoot=$shareRoot
)
$folderName=Split-Path $path -Leaf
#Write-Host "junction '$smbShareRoot\$folderName' '$path'"
invoke-expression "junction '$smbShareRoot\$folderName' '$path'"
}

foreach ($path in $paths){
if ($path.Path -notlike "$shareRoot*"){
Write-Host "Creating juncion for $($path.Path)";
createJunction -path $path.Path -smbShareRoot $shareRoot;
}
}

PowerShell: Benchmark Disk Speed

function getDiskSpeed{
<#
Purpose: to measure IOPS of local volumes or UNC paths
- This function must be ran with Administrator privileges, especially when triggered for the first time on a local system
- Two utilities will be required and installed on the system: chocolatey (DevOps Tool) and diskspd.exe (Microsoft's disk benchmark app, similar to SQLIO)
- Call the function with a valid path to test disk speed, such as:
getDiskSpeed C:\
getDiskSpeed \\FILESHERVER01\TESTDIR1
getDiskSpeed D
#>
param(
[string]$driveLetter="c:",
[int]$sampleSize=5
)
if($driveLetter.Length -eq 1){$driveLetter+=":";}

function isPathWritable{
param($testPath)
# Create random test file name
$tempFolder=$testPath+"\getDiskSpeed\"
$filename = "diskSpeedTest-"+[guid]::NewGuid()
$tempFilename = (Join-Path $tempFolder $filename)
New-Item -ItemType Directory -Path $tempFolder -Force -EA SilentlyContinue|Out-Null

Try {
# Try to add a new file
# New-Item -ItemType Directory -Path $tempFolder -Force -EA SilentlyContinue
[io.file]::OpenWrite($tempFilename).close()
#Write-Host -ForegroundColor Green "$testPath is writable."

# Delete test file after done
# Remove-Item $tempFilename -Force -ErrorAction SilentlyContinue

# Set return value
$feasible=$true;
}
Catch {
# Return 'false' if there are errors
$feasible=$false;
}

return $feasible;
}

# Check if input is a valid drive letter
function validatePath{
param([string]$path=$driveLetter)
if (Test-Path $path -EA SilentlyContinue){
$regexValidDriveLetters="^[A-Za-z]\:{0,1}$"
$validLocalPath=$path.SubString(0,2) -match $regexValidDriveLetters
if ($validLocalPath){
write-Host "Local directory detected."
$driveLettersOnThisComputer=ls function:[A-Z]: -n|?{test-path $_}
if (!($driveLettersOnThisComputer -contains $path.SubString(0,2))){
Write-Host "The provided local path's first 2 characters do not match any volumes in this system.";
return $false;
}
return $(isPathWritable $path)
}else{
$regexUncPath="^\\(?:\\[^<>:`"/\\|?*]+)+$"
if ($path -match $regexUncPath){
write-Host "UNC directory detected."
return $(isPathWritable $path)
}else{Write-Host "The provided path does not match a UNC pattern nor a local drive.";return $false;}
}
}else{
Write-Host "The path $path currently does NOT exist";
Return $false;
}
}

if (validatePath){

# Set variables
$tempDirectory="$driveLetter`\getDiskSpeed"
# New-Item -ItemType Directory -Force -Path $tempDirectory|Out-Null
$testFile="$tempDirectory`\testfile.dat"
$processors=(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors

# Ensure that diskspd.exe is available in the system
$diskSpeedUtilityAvailable=get-command diskspd.exe -ea SilentlyContinue
if (!($diskSpeedUtilityAvailable)){
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
choco install diskspd -y;
refreshenv;
}

function getIops{
# Sometimes, the test result throws this error "diskspd Error opening file:" if no switches were used
# The work around is to specify more parameters
# Other variations:
# $testResult=diskspd.exe-d1 -o4 -t4 -b8k -r -L -w50 -c1G $testFile
# $testResult=diskspd.exe -b4K -t1 -r -w50 -o32 -d10 -c8192 $testFile
# Note: remove the -c option to avoid this error when running with unprivileged accounts
# diskspd.exe : WARNING: Error adjusting token privileges for SeManageVolumePrivilege (error code: 1300)

try{
$testResult=invoke-expression "diskspd.exe -b8k -d1 -o$processors -t$processors -r -L -w25 -c1G $testfile";
<# diskspd.exe -b8k -d1 -o4 -t4 -r -L -w25 -c1G $testfile
8K block size; 1 second random I/O test;4 threads; 4 outstanding I/O operations;
25% write (implicitly makes read 75% ratio);
#>
}
catch{
$errorMessage = $_.Exception.Message
$failedItem = $_.Exception.ItemName
Write-Host "$errorMessage $failedItem";
continue;
}
$x=$testResult|select-string -Pattern "total*" -CaseSensitive|select-object -First 1|out-String
$iops=$x.split("|")[-3].Trim()
#$mebibytesPerSecond=$x.split("|")[-4].Trim()
return $iops
}

function selectHighIops{
$testArray=@();
for($i=1;$i -le $sampleSize;$i++){
try{
$testArray+=getIops;
}
catch{
$errorMessage = $_.Exception.Message
$failedItem = $_.Exception.ItemName
Write-Host "$errorMessage $failedItem";
break;
}
}
$highestResult=($testArray|measure -Maximum).Maximum
return $highestResult
}

# Trigger several tests and select the highest value
$selectedIops=selectHighIops

# Cleanup
# cmd /c rd $tempDirectory
function isFileLocked{
param($file=$(New-Object System.IO.FileInfo $testFile))
if (Test-Path $testFile){
try {
$fileHandle = $file.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($fileHandle){
# File handle is open, which means file is not locked
$fileHandle.Close()
}
return $false
}
catch{
# file is locked
return $true
}
}else{return $false}
}

do {
sleep 1;
isFileLocked|out-null;
}until(!(isFileLocked))
Remove-Item -Recurse -Force $tempDirectory

$mebibytesPerSecond=[math]::round($(([int]$selectedIops)/128),2)
return "$mebibytesPerSecond MiB/s ($selectedIops IOPS)";
}else{return "Cannot get disk speed"}
}
getDiskSpeed;

Bash Shell: Old School Migration of ESXi Guest Virtual Machines

Step 0: Preparations

Estimate file-copying duration for cut-over:
– Sustained storage transfer speed using a 1GB Ethernet Connection is approximately 99.12 MB/s (Mebibytes per second)
– To move 1GB (1024MB) of storage, it takes 10.33 seconds
– 100GB will require 1033 seconds (17.22 minutes)

Perform some systems discovery on ESXi hosts:

# The quick method of listing available volumes
mountDirectory=/vmfs/volumes
availableVolumes=$(echo "ls $mountDirectory"|readlink -f $_)

# Alternative and more convoluted method of listing available volumes
mountDirectory=/vmfs/volumes
symlinkMarker="\->"
symlinks=$(ls -la $mountDirectory/ | grep $symlinkMarker)
availableVolumes=$(echo $symlinks | eval "sed -n -e 's/^.*$symlinkMarker //p'"|echo $mountDirectory/$_)
# Explain:
# - Pipe $symlinks to eval to execute an expression. Similar to Invoke-Command in Powow Shill
# - Sed is the go-to tool for search and replace
# - -n to default as not to print anything
# - -e follows the sed command as default syntax. It probably stands for evaluate
# - s is to search and replace
# - /^.* means all characters from beginning of string toward delimiter of $symlinkMarker as the $matches
# - // means empty string as replacement for the $matches
# - p is to print the result
# - Last section is to append $mountDirectory to the sub-directory names

# List mounts
echo $availableVolumes

# List contents of each mount. Please note that explicit casting of variables with double quotes is a
# requirement for pipe operations, allowing processing of such variables line-by-line, rendering $_ variable as per line
echo "$availableVolumes" | ls $_

Define these variables:

# Define source variables
SOURCESERVER=10.10.10.101
SOURCESERVERPORT=22
SOURCEUSERNAME=brucelee
sourceStoragePath=/vmfs/volumes/5ed459d2-8f5430cd-3762-94c691ac4caa
sourceDirectoryName=Windows-PC1
SOURCEDIRECTORY=$sourceStoragePath/$sourceDirectoryName/
SOURCEFILES=$SOURCEDIRECTORY*.*

# Define destination variables
DESTINATIONSERVER=10.10.10.100
DESTINATIONSERVERPORT=22
DESINATIONUSERNAME=brucelee
destinationStoragePath=/vmfs/volumes/5c8206f0-49fe1606-76b6-94c691abb6e2
destinationDirectoryName=Windows-PC1
DESTINATIONDIRECTORY=$destinationStoragePath/$destinationDirectoryName/

vmxName=$destinationDirectoryName.vmx
vmdkName=$destinationDirectoryName.vmdk
VMXFILE=$DESTINATIONDIRECTORY$vmxName
VMDKFILE=$DESTINATIONDIRECTORY$vmdkName

resizedVmdkName=$destinationDirectoryName-resized.vmdk
resizedVmdk=$DESTINATIONDIRECTORY$resizedVmdkName

Remove any localized resources on the reference VM that the destination server does not have access, such as:
– Virtual disks
– Virtual networks
– ISO mounts
– USB pass-through dongles

Step 1: Ensure that SSH Server is running at Destination and SSH Client is running at Source

SSH Server Settings:
Host > Manage > Services > right-click the SSH service with alias “TSM-SSH” > Start SSH service (if not already started)

SSH Client Settings:
Networking > Firewall rules > right-click SSH Client > “Enable” (if not already enabled)

Step 2: Sanitize the destination
# ssh into Destination server
ssh $DESINATIONUSERNAME@$DESTINATIONSERVER$DESTINATIONSERVERPORT
# Optional: create the destination directory if not exists
mkdir -p $destinationStoragePath/$destinationDirectoryName
# Optional: Empty destination directory
rm -r $DESTINATIONDIRECTORY*
Step 3: Check SSH Services while login to a SSH session of the Source Server
# ssh into Source server
ssh $SOURCEUSERNAME@$SOURCESERVER -p$SOURCESERVERPORT
# Verify Destination SSH reachability
ssh $DESINATIONUSERNAME@$DESTINATIONSERVER -p$DESTINATIONSERVERPORT "echo 2>&1" && echo $DESTINATIONSERVER OK || echo $DESTINATIONSERVER NOT-OK
Step 4: Perform the copy
# ssh into Source server
ssh $SOURCEUSERNAME@$SOURCESERVER -p$SOURCESERVERPORT
# Use SCP to copy files
scp -P $DESTINATIONSERVERPORT $SOURCEFILES $DESINATIONUSERNAME@$DESTINATIONSERVER:$DESTINATIONDIRECTORY
Step 5: Register the Migrated VM on New Host
# ssh into Destination server
ssh $DESINATIONUSERNAME@$DESTINATIONSERVER$DESTINATIONSERVERPORT
# Register VM with this host
vmid=$(vim-cmd solo/registervm $VMXFILE)

$ Power On VM
# List all VMs: vim-cmd /vmsvc/getallvms
# Check VM power state: vim-cmd /vmsvc/power.getstate $vmid
vim-cmd vmsvc/power.on $vmid

# If this machine is freshly registered on this host, it will require a confirmation that it has been "copied" or "moved" via the ESXi GUI. In such context, migrated correlates to moved.

Sample Output:

[brucelee@esx02:~] echo $VMXFILE
/vmfs/volumes/5ed456fa-5daa2d25-e3bd-94c691ac4caa/Windows-PC1/Windows-PC1.vmx
[brucelee@esx02:~] vim-cmd solo/registervm $VMXFILE
3

Optional: resize the main disk and attach the resized

# Convert from thick to thin provisioning by cloning the original disk to a thin-provisioned copy
vmkfstools -i $VMDKFILE -d thin $resizedVmdk

# Reset the resizing virtual disk to a $desiredDiskSize:
desiredDiskSize=120g
vmkfstools -X $desiredDiskSize $resizedVmdk

# Detach the original disk
# Usage: device.diskremove vmid 'controller number' 'unit number' 'delete file'(y/n)
vim-cmd vmsvc/device.diskremove $vmid 0 0 n

# Attach resized disk to scsi controller zero disk index 0 (or any available index)
vim-cmd vmsvc/device.diskaddexisting $vmid $resizedVmdk 0 0
Step 6: Unregister VM from Old Host
# ssh into Source server
ssh $SOURCEUSERNAME@$SOURCESERVER -p$SOURCESERVERPORT
# Check all VMs
[brucelee@esx02:~] vim-cmd vmsvc/getallvms
Vmid Name File Guest OS Version Annotation
2 Windows-PC1 [Micron-SSD-894GB] Windows-PC1/Windows-PC1.vmx windows9_64Guest vmx-14

# Set $vmid according to result(s) above
vmid=2

# Unregister VM
[brucelee@esx02:~] vim-cmd /vmsvc/unregister $vmid

Easy lap, right? This is a lot simpler than a 10-page of GUI screenshots, more likely to save your Change Control Board members’ time so they can enjoy other items on the agenda. With such competence and efficiency, one could even move to entertain Boardies with Operation Kung Fu Panda (taking breaks for Chinese food), or something.

PowerShell: List Currently Logon Users On Remote Servers

# Show current sessions on a list of servers
$servers="SHERVER005","SHERVER007";
$servers|%{"$_`n$(query user /server:$_|Out-String)"}
# Sample Output
PS C:\Windows\system32> $servers|%{"$_`n$(query user /server:$_|Out-String)"}
SHERVER005
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
user001 6 Disc 9+08:54 10/10/2019 4:42 AM
user002 9 Disc 3+01:12 10/16/2019 12:56 PM
user003 15 Disc 1:48 10/17/2019 2:29 PM
user004 21 Disc 2:34 10/19/2019 7:52 AM
user005 rdp-tcp#40 22 Active 2:38 10/19/2019 8:21 AM
user006 rdp-tcp#43 23 Active . 10/19/2019 2:02 PM

SHERVER007
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
user007 rdp-tcp#8 3 Active 2:38 10/18/2019 10:01 PM
user008 4 Disc 5:43 10/19/2019 7:52 AM
user009 5 Disc 2:51 10/19/2019 10:08 AM
user010 6 Disc 1:48 10/19/2019 11:18 AM

PowerShell: Automating Process of Setting Up a New Windows Machine

# Step 0: Activate Windows
$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
}
activateWindows;
# Step 1: Rename the local machine while remaining with its default workgroup
Rename-Computer -NewName $newServername -Force
Restart-Computer -Force
# Step 2: Find the OU of an intended peer server
$peerServername="SHEVER002"
Move-ADObject $peerServername -TargetPath "OU=Quarantine,DC=INTRANET,DC=KIMCONNECT,DC=COM" -WhatIf
# Step 3: Join new machine to domain
$newServername="SHERVER007"
$domain="INTRANET.KIMCONNECT.COM"
$desktopAdmin="Rambo"
$ouPath= "OU=Servers,DC=INTRANET,DC=KIMCONNECT,DC=COM"
$cred = Get-Credential "$domain`\$desktopAdmin"
add-computer -computername $newServername –Domain $domain -Credential $cred -OUPath $ouPath -restart –force
# Step 4: Install  Choco and update PowerShell
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
$packages = 'powershell','googlechrome','firefox','adobereader','7zip.install','javaruntime','putty.install','sysinternals'
ForEach ($package in $packages){choco install $package -y};
Restart-Computer -Force;
# Step 5: Perform Windows Update
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."}
}
updateLocalWindows;
# Step 6: Remediate Common Vulnerabilities
function remediateVulnerabilities{
"`nVCE-2017-829..."
reg add "HKLM\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX" /v iexplore.exe /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX" /v iexplore.exe /t REG_DWORD /d 1 /f

"`nCVE-2017-5715..."
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverride /t REG_DWORD /d 0 /f
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f

"`nVCE-2017-5753-54..."
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization" /v MinVmVersionForCpuBasedMitigations /t REG_SZ /d "1.0" /f

"`nASLR Hardening Setting for IE..."
reg add "HKLM\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING" /v iexplore.exe /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING" /v iexplore.exe /t REG_DWORD /d 1 /f

"`nRemediate MS11-025 MFC Remote Code Execution..."
$minVersion=14
$vcVersions=(Get-ItemProperty HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | where {$_.displayname -like "Microsoft Visual C++*"} | Select-Object DisplayVersion)
foreach ($version in $vcVersions){
if($version.DisplayVersion -ge $minVersion){$safeFlag=$True;}
}
if (!($safeFlag)){
try{
New-Item -ItemType Directory -Force -Path C:\Temp
(new-object System.Net.WebClient).DownloadFile('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe','C:\Temp\vcredist_x64.exe')
C:\Temp\vcredist_x64.exe /quiet /norestart
}
catch{
"Unable to download Visual C++"
}
}

"`nSecuring Remote Desktop..."
function secureRDP{
# Remote Desktop Services: Enable NLA Requirement
(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(1)

# Remote Desktop Services: Require 'High' level of encryption - FIPS compliant
(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").SetEncryptionLevel(4)
}
secureRDP;

"`nUpdating Windows Defender..."
try{"`nUpdating Windows Defender Antimalware Virus Definitions...";Update-MPSignature;}
catch{"Cannot update Windows Defender."}

"`nFix Unquoted Service Path Enumerations..."
function fixUnquotedServicePathEnum{
$fixScriptDestination="C:\Temp\Windows_Path_Enumerate.ps1"
$fixScriptDownload="https://gallery.technet.microsoft.com/scriptcenter/Windows-Unquoted-Service-190f0341/file/136821/7/Windows_Path_Enumerate.ps1"
(new-object System.Net.WebClient).DownloadFile($fixScriptDownload, $fixScriptDestination)
C:\Temp\Windows_Path_Enumerate.ps1 -FixUninstall -FixEnv
}
fixUnquotedServicePathEnum;

"`nApplying IIS Crypto Templates..."
function installIISCrypto{
# Download iisCrypto
New-Item -ItemType Directory -Force -Path C:\Temp
$url = "https://kimconnect.com/wp-content/uploads/2019/05/IISCryptoCli.zip"
$temp = "C:\Temp\IISCryptoCli.zip"
(new-object System.Net.WebClient).DownloadFile($url,$temp)

$destination="C:\Windows"
expand-archive -path $temp -destinationpath $destination
}
installIISCrypto;

function applyIISCrypto{
$templateValues="pci32","best","strict","fips140","default"
$count=$templateValues.length
for ($i=0; $i -lt $count; $i++) { $template=$templateValues[$i];"$i`: $template"}
$index=Read-Host -Prompt "Type in the Template Index NUMBER from 0 to $($count-1) to apply"
if ($index -lt $count){
$iisCryptoExist=([System.IO.File]::Exists("C:\Windows\IISCryptoCli.exe"))
if ($iisCryptoExist){
$choice=$templateValues[$index]
"`nBacking up registry into C:\backup.reg, and applying $choice template..."
IISCryptoCli /backup C:\backup.reg /template $choice;
"`nIISCrypto $choice template has been applied...`nPlease reboot machine for changes to take effect."
}
else{
"`nIISCryto wasn't installed. Retrying..."
installIISCripto;
applyIISCrypto;
}
}
else{"Index number was not recognized. Thus, IISCrypto Template was NOT applied."}
}
applyIISCrypto;

# Disable SMB1
Disable-WindowsOptionalFeature -Online -FeatureName smb1protocol -InformationAction SilentlyContinue -NoRestart

}
# Step 7: Optimize & Clean Windows
function optimizeWindows{
"`nSet Power Options..."
function setPowerMax{
powercfg /setactive SCHEME_MIN
powercfg /hibernate off
}
setPowerMax;

"Disable Automatic Startup Repair..."
cmd.exe /c "bcdedit /set {default} recoveryenabled No"
cmd.exe /c "bcdedit /set {default} bootstatuspolicy ignoreallfailures"
}

function removeBloatware{
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$bloatwareRemovalDownload="https://github.com/Sycnex/Windows10Debloater/archive/master.zip"
$bloatwareRemovalDestination="C:\Temp\Windows10Debloater-master.zip"
(New-Object System.Net.WebClient).DownloadFile($bloatwareRemovalDownload, $bloatwareRemovalDestination)
$destination="C:\Temp"
expand-archive -path $bloatwareRemovalDestination -DestinationPath $destination
PowerShell.exe -executionpolicy bypass -File C:\Temp\Windows10Debloater-master\Windows10Debloater.ps1 -Confirm:$False

# Disable Windows Media (a vector of attack surface from malware)
Disable-WindowsOptionalFeature –FeatureName "WindowsMediaPlayer" -Online

# Disable XPS
Disable-WindowsOptionalFeature -Online -FeatureName "Printing-XPSServices-Features"

# Workfolder Client
Disable-WindowsOptionalFeature -Online -FeatureName "WorkFolders-Client"

# Remove Windows Store
Get-AppxPackage -AllUsers | Where-Object {$_.Name -like "Microsoft.WindowsStore*"} | remove-appxpackage
}
removeBloatware;

function cleanWindows{
"Clear Windows Update Cache..."
Dism.exe /online /Cleanup-Image /StartComponentCleanup

"Delete files in Temp directory..."
del C:\Temp\*.* -Recurse -Force

"Prune Event Logs..."
wevtutil el | Foreach-Object {wevtutil cl "$_"}

"Performing Disk Cleanup..."
$HKLM = [UInt32] "0x80000002"
$strKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches"
$strValueName = "StateFlags0065"

$subkeys = gci -Path HKLM:\$strKeyPath -Name
ForEach ($subkey in $subkeys) {
New-ItemProperty -Path HKLM:\$strKeyPath\$subkey -Name $strValueName -PropertyType DWord -Value 2 -ErrorAction SilentlyContinue| Out-Null
Start-Process cleanmgr -ArgumentList "/sagerun:65" -Wait -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
ForEach ($subkey in $subkeys) {
Remove-ItemProperty -Path HKLM:\$strKeyPath\$subkey -Name $strValueName | Out-Null
}
}
cleanWindows;

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. Here are some examples.
# Better Illustration of workaround #3
function validateData{
    param($inputData=$sourcesAndDestinations)
    $objectLength=$inputData.from.length 
    for ($i=0;$i -lt $objectLength; $i++){        
        $from=$inputData.from[$i]
        $to=$inputData.to[$i]
        #"Checking $from and $to ..."
        #if(!(test-path $from) -OR !(test-path $to) ){
        if(!(test-path $to)){
            write-host "$to is not reachable."    
            # Remove row if any path doesn't resolve. Overcome limitations of Powershell's immutable array "fixed size" using this workaround
            $castedArrayList=[System.Collections.ArrayList]$inputData;
            $castedArrayList.RemoveAt($i);
            $inputData=[Array]$castedArrayList; #reversed the casting
            #$inputData.from = $inputData.from|?{$_ -ne $from}
            #$inputData.to = $inputData.to|?{$_ -ne $to}
            $objectLength--;
            $i--;
            } #else{write-host "$to is reachable."}
    }
    return $inputData
}

$sourcesAndDestinations=validateData -inputData $sourcesAndDestinations
# 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