PowerShell: Add and Remove a Registry Key

How to Add a Registry Key

# Add New Registry Key
$regHive='REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSCRM'
$keyname='OleDbTimeout'
$value=600
Set-ItemProperty -Path $regHive -Name $keyname -value $value

# Validation
Get-ItemProperty $regHive -name $keyname

PS C:\Users\backupadmin> Get-ItemProperty $regHive -name $keyname
OleDbTimeout : 600
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSCRM
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
PSChildName  : MSCRM
PSProvider   : Microsoft.PowerShell.Core\Registry

How to Remove a Registry Key

# Remove Registry Key
$regHive='REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSCRM'
$keyname='OleDbTimeout'
Remove-ItemProperty -Path $regHive -Name $keyname -value $value

PowerShell: Set Service Startup Mode

# setServiceStartupMode.ps1

$computernames=@(
    'TESTWINDOWS001',
    'TESTWINDOWS002'
)
$servicename='TrustedInstaller'
$startupType='Automatic'

foreach ($computername in $computernames){
    Set-Service -computername $computername -Name $servicename -StartupType $startupType
    Get-Service -computername $computername -Name $servicename|start-service
}

Get-Service -computername $computernames -Name $servicename

PowerShell: Move Virtual Machine Storage Using VMM

# moveVmStorageUsingVmm.ps1
# Version 0.01

$vmNames=@(
  'TESTVM0001',  
  'TESTVM0002',
  'TESTVM0003'
)
$storageLocations=@(
  'C:\ClusterStorage\BLOB001',
  'C:\ClusterStorage\BLOB002',
  'C:\ClusterStorage\BLOB003'
)
$storageMaxPercent=75
$confirmation=$true

function moveVmStorageUsingVmm($vmName,$newStorage,$storageMaxPercent=75){  
  try{
    $vmHosts=Get-SCVMHost
    $vm=Get-SCVirtualMachine -Name $vmName
    if($vm.count -eq 1){
      $currentHost=$vm.Hostname
      $storage=Get-SCStorageVolume -VMHost $currentHost|?{$_.Name -eq $newStorage}
      $capacity=$storage.Capacity
      $freespace=$storage.FreeSpace
      $storageUtilizedPercent=[math]::round(($capacity-$freespace)/$capacity*100,2)
      $totalSize=0
      $disks = Get-SCVirtualDiskDrive -VM $vmname
      $disks.VirtualHardDisk.Size|%{$totalSize+=$_}      
      $projectedPercent=[math]::round(($capacity-$freespace+$totalSize)/$capacity*100,2)
      write-host "$newStorage current utilization percentage: $storageUtilizedPercent`% and projected: $projectedPercent`%"
      $storageFeasible=if($projectedPercent -lt $storageMaxPercent){$true}else{$false}
      if($storageFeasible){
        $vmHost=$vmHosts|?{$_.Name -eq $currentHost}
        Move-SCVirtualMachine -VM $vm -VMHost $vmHost -Path $newStorage -UseLAN -UseDiffDiskOptimization # -RunAsynchronously
      }else{
        write-warning "Infeasible storage location: Available storage volume is $storageMaxPercent`% and projected is $projectedPercent`%"
        return $false
      }
    }else{
      write-warning "$vmName matches more than 1 guest VM's; hence, this item is skipped."
    }
    return $true
  }catch{
    write-warning $_
    pause
  }
}

$storageIndex=0
$useSameStorage=$true
for($i=0;$i -lt $vmNames.count;$i++){
  $vmName=if($useSameStorage){$vmNames[$i]}else{$vmNames[--$i]}
  $storageLocation=if($useSameStorage){$storageLocations[$storageIndex]}else{$storageLocations[++$storageIndex]}
  if($storageLocation){
    if($confirmation){
      write-host "Move $vmName storage to $storageLocation`?"
      pause
    }else{
      write-host "Moving $vmName storage to $storageLocation ..."
    }
    $useSameStorage=moveVmStorageUsingVmm $vmName $storageLocation $storageMaxPercent 
  }else{
    write-warning "Exhausted storage locations to move VM's (shit is out of range)"
  }
}

PowerShell: Add Windows NTFS Permissions to File or Folder

The re-usable function:

$path='C:\Windows\servicing'
$accountsToAdd='Administrators'
$permissions='Full'

function addNtfsPermissions ($path,$accountsToAdd,$permissions){
  $acl = Get-ACL $path
  $accessRule=New-Object System.Security.AccessControl.FileSystemAccessRule($accountsToAdd,$permissions,"Allow")
  $acl.AddAccessRule($accessRule)
  Set-Acl $path $acl
  Get-ACL $path
}

addNtfsPermissions $path $accountsToAdd $permissions

Example:

The following is an output of fixing an issue related to ‘TrustedInstaller will not run. Windows Module Installer service missing’ errors.

$computernames=@(
  'TESTWINDOWS1',
  'TESTWINDOWS2'
  )

$path='C:\Windows\servicing'
$accountsToAdd='Administrators'
$permissions='Full'

function addNtfsPermissions ($path,$accountsToAdd,$permissions){
  $acl = Get-ACL $path
  $accessRule=New-Object System.Security.AccessControl.FileSystemAccessRule($accountsToAdd,$permissions,"Allow")
  $acl.AddAccessRule($accessRule)
  Set-Acl $path $acl
  Get-ACL $path
}

foreach($computername in $computernames){
  invoke-command -computername $computername -scriptblock{
    param($addNtfsPermissions,$path,$accountsToAdd,$permissions)
    write-host "Invoking function on $env:computername"
    [scriptblock]::create($addNtfsPermissions).invoke($path,$accountsToAdd,$permissions)
    start-service trustedinstaller
    get-service trustedinstaller
  } -ArgumentList ${function:addNtfsPermissions},$path,$accountsToAdd,$permissions
}
[TESTWINDOWS]: PS C:\Users\kimconnect\Documents> Get-ACL $localPath|select *

PSPath                  : Microsoft.PowerShell.Core\FileSystem::C:\Windows\servicing
PSParentPath            : Microsoft.PowerShell.Core\FileSystem::C:\Windows
PSChildName             : servicing
PSDrive                 : C
PSProvider              : Microsoft.PowerShell.Core\FileSystem
CentralAccessPolicyId   :
CentralAccessPolicyName :
Path                    : Microsoft.PowerShell.Core\FileSystem::C:\Windows\servicing
Owner                   : NT SERVICE\TrustedInstaller
Group                   : NT SERVICE\TrustedInstaller
Access                  : {System.Security.AccessControl.FileSystemAccessRule,
                          System.Security.AccessControl.FileSystemAccessRule,
                          System.Security.AccessControl.FileSystemAccessRule,
                          System.Security.AccessControl.FileSystemAccessRule...}
Sddl                    : O:S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464G:S-1-5-80-956008885-34185226
                          49-1831038044-1853292631-2271478464D:PAI(A;OICIIO;GXGR;;;SY)(A;;0x1200a9;;;SY)(A;;FA;;;BA)(A;
                          OICIIO;GXGR;;;BA)(A;OICIIO;GXGR;;;BU)(A;;0x1200a9;;;BU)(A;OICIIO;GA;;;S-1-5-80-956008885-3418
                          522649-1831038044-1853292631-2271478464)(A;;FA;;;S-1-5-80-956008885-3418522649-1831038044-185
                          3292631-2271478464)(A;;0x1200a9;;;AC)(A;OICIIO;GXGR;;;AC)(A;;0x1200a9;;;S-1-15-2-2)(A;OICIIO;
                          GXGR;;;S-1-15-2-2)
AccessToString          : NT AUTHORITY\SYSTEM Allow  -1610612736
                          NT AUTHORITY\SYSTEM Allow  ReadAndExecute, Synchronize
                          BUILTIN\Administrators Allow  FullControl
                          BUILTIN\Administrators Allow  -1610612736
                          BUILTIN\Users Allow  -1610612736
                          BUILTIN\Users Allow  ReadAndExecute, Synchronize
                          NT SERVICE\TrustedInstaller Allow  268435456
                          NT SERVICE\TrustedInstaller Allow  FullControl
                          APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow  ReadAndExecute, Synchronize
                          APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow  -1610612736
                          APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES Allow  ReadAndExecute,
                          Synchronize
                          APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES Allow  -1610612736
AuditToString           :
AccessRightType         : System.Security.AccessControl.FileSystemRights
AccessRuleType          : System.Security.AccessControl.FileSystemAccessRule
AuditRuleType           : System.Security.AccessControl.FileSystemAuditRule
AreAccessRulesProtected : True
AreAuditRulesProtected  : False
AreAccessRulesCanonical : True
AreAuditRulesCanonical  : True

[TESTWINDOWS]: PS C:\Users\kimconnect\Documents> get-service trustedinstaller
Status   Name               DisplayName
------   ----               -----------
Stopped  trustedinstaller   Windows Modules Installer

[TESTWINDOWS]: PS C:\Users\kimconnect\Documents> get-service trustedinstaller|start-service
[TESTWINDOWS]: PS C:\Users\kimconnect\Documents> get-service trustedinstaller
Status   Name               DisplayName
------   ----               -----------
Running  trustedinstaller   Windows Modules Installer

PS C:\Windows\system32> get-service -Name trustedinstaller -ComputerName $computernames|start-service

PS C:\Windows\system32> get-service -Name trustedinstaller -ComputerName $computernames|select MachineName,ServiceName,S
tartType,Status

MachineName     ServiceName      StartType  Status
-----------     -----------      ---------  ------
TESTWINDOWS0001 trustedinstaller    Manual Running
TESTWINDOWS0002 trustedinstaller    Manual Running

Search for Windows computers in a certain subnet using Active Directory

# Search for Windows computers in a certain subnet using Active Directory
$subnetQuery='10.10'
$filterString='2016'
$computers=Get-ADComputer -Filter "enabled -eq 'true' -and OperatingSystem -like '*$filterString*'"`
-Properties Name,Operatingsystem,OperatingSystemVersion,IPv4Address |
Sort-Object -Property Operatingsystem | Select-Object -Property Name,Operatingsystem,OperatingSystemVersion,IPv4Address
$results=$computers|?{$_.IPv4Address|?{[string]$_ -like "*$subnetQuery*"}}
write-host "$($results.count) computer(s) have been matched.`r`n$results"
# Sample output
Name            Operatingsystem              OperatingSystemVersion IPv4Address
----            ---------------              ---------------------- -----------
SERVER-NAME-001 Windows Server 2016 Standard 10.0 (14393)           10.10.120.9
SERVER-NAME-002 Windows Server 2016 Standard 10.0 (14393)           10.10.120.10
------ Results truncated ---------

PowerShell: Create Hyper-V Guest VM From Virtual Disk (VHDX)

Part 1: Creating Hyper-V Guest VM From a Virtual Disk

# createHyperVGuestVmFromDisk.ps1
# Version 0.02
# The intent of this script is to create a Hyper-V Guest VM basing on existing backup VHDX file(s)

$sourceVhdx='\\FILESERVER008\_Images\Windows2019_Image.vhdx'
$destinationFolder='\\VIRTUALMACHINES\STAGE'
$network='External-Connection'
$vlan='3000'

$newVmName='TESTVM009'
$memory='8GB'
$cpus=4
$secureBoot=$true # Windows: True, Linux: False
$extraDisks=@($null) # $extraDisks=@('test')
$generation=2
$deleteSourceDisks=$true
$onlineVm=$true

function createHyperVGuestVmFromDisk{
  param(
      $sourceVhdx,
      $newVmName,
      $destinationFolder,
      $memory='4GB',
      $cpus=2,
      $network,
      $vlan,
      $extraDisks=$null,
      $generation=2,
      $secureBoot=$false,
      $onlineVm=$true,
      $deleteSourceDisks=$false
  )
  $ErrorActionPreference = 'stop'
  
  function confirmation($content,$testValue="I confirm",$maxAttempts=3){
    $confirmed=$false
    $cancelCondition=@('cancel','no','exit','nope')
    $attempts=0
    clear-host 
    write-host $($content|out-string).trim()
    write-host "`r`nPlease review this content for accuracy.`r`n"
    while ($attempts -le $maxAttempts){
        if($attempts++ -ge $maxAttempts){
            write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
            break;
            }
        $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm. Input CANCEL to skip this item";
        if ($userInput.ToLower() -eq $testValue.ToLower()){
            $confirmed=$true;
            write-host "Confirmed!`r`n";
            break;                
        }elseif($userInput.tolower() -in $cancelCondition){
            write-host 'Cancel command received.'
            $confirmed=$false
            break
        }else{
            clear-host
            $content|write-host
            write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again or Input CANCEL to skip this item`r`n"
            }
        }
    return $confirmed
  }

  try{
      $newFolder="$destinationFolder\$newVmName"
      if(!(test-path $newFolder)){new-item -ItemType Directory -Path $newFolder -force}              
      $newVhdx="$newFolder\$newVmName`_disk0.vhdx"
      if(!(test-path $newVhdx)){
          New-Item -ItemType File -Path $newVhdx -Force # touch before copying contents
          Copy-Item -Path $sourceVhdx -Destination $newVhdx
      }else{
          write-warning "Volume $newVhdx already exists. Thus, that VMDK will be used instead of a clone."
      }
      New-VM -Name $newVmName `
              -MemoryStartupBytes $($memory/1) `
              -BootDevice VHD `
              -VHDPath $newVhdx `
              -Path $newFolder `
              -Generation $generation `
              -Switch $network         
      if($vlan){Set-VMNetworkAdapterVlan -VMName $newVmName -Access -VlanId $vlan}
              if($cpus -gt 1){Set-VMProcessor $newVmName -Count $cpus}
      Set-VMProcessor $newVmName -CompatibilityForMigrationEnabled $true
      if(!$secureBoot){Set-VMFirmware -VMName $newVmName -DisableSecureBoot}
      # Adding disks (optional)
      if($extraDisks){
          for($i=0;$i -lt $extraDisks.count;$i++){
            $extraDisk=$extraDisks[$i]
            $isvalidDisk=if(test-path $extraDisk){$extraDisk}else{$null}
            if($isvalidDisk){
              $newDiskPath=(join-path $destinationFolder $vmName) + "\$vmName`_disk$($i+1).vmdk"
              if(!(test-path $newDiskPath)){
                New-Item -ItemType File -Path $newDiskPath -Force # touch before copying contents
                Copy-Item -Path $extraDisk -Destination $newDiskPath
              }else{
                  write-warning "Volume $newDiskPath already exists. Thus, that VMDK will be used instead of a copy."
              }              
              Add-VMHardDiskDrive -VMName $newVmName -Path $newDiskPath 
            }else{
              write-warning "Disk path '$extraDisk' in invalid."
            }
          }
      }else{
        write-host "$newVmName has no extra disks to attach."
      }
      $disksToRemove=[array]$extraDisks+$sourceVhdx|?{$_} # join string to array and remove empty entries
      foreach($diskToRemove in $disksToRemove){
        $confirmed=confirmation "Remove source disk $diskToRemove"
        if($confirmed){
          remove-item $diskToRemove -force
        }else{
          write-host "$diskToRemove NOT removed."
        }
      }
      if($onlineVm){
          start-vm $newVmName
      }
      return $true
  }catch{
      write-warning "$($error[0])"
      return $false
  }
}

createHyperVGuestVmFromDisk $sourceVhdx `
  $newVmName `
  $destinationFolder `
  $memory `
  $cpus `
  $network `
  $vlan `
  $extraDisks `
  $generation `
  $secureBoot `
  $onlineVm `
  $deleteSourceDisk

Part 2: Adding New VM to Cluster

function addVmToCluster{
    param($vmNames,$targetCluster)
    $results=@()
    foreach ($vmName in $vmNames){
        try{
            #Start-VM -Name $vmName -EA Stop
            if(!$targetCluster){$targetCluster=(get-cluster -ea SilentlyContinue).Name}
            if($targetCluster){
                $null=Add-ClusterVirtualMachineRole -Cluster $targetCluster -VirtualMachine $vmName -EA Stop
                $results+=[hashtable]@{$vmName=$true}
            }else{
                write-host "No clusters defined."
                $results+=[hashtable]@{$vmName=$false}
                }
            $moved=if(get-cluster -ea SilentlyContinue){Move-ClusterVirtualMachineRole $newVmName}else{$false}
            if($moved){write-host "'$newVmname' has been moved to $($moved.OwnerNode)"}
        }catch{
            write-warning "$($error[0])"
            $results+=[hashtable]@{$vmName=$false}
            }
    }
    return $results
}

addVmToCluster $newVmName

Possible Error Message:

Microsoft Hyper-V UEFI

Virtual Machine Boot Summary

1.SCSI Disk (0,0) the boot loader did not load an operating system
2. Network adapter (00155D406142) a boot image was not found

No operating system was loaded. Your virtual machine may be configured incorrectly. Exit and rec-configure your VM or click restart to retry the current boot sequence again.

Resolution:

The case where this error has been thrown has been associated with an incorrect virtual machine generation type. Hence, the resolution has been:

A. Convert Generation 2 machine type back to Generation 1 as the original source disk VM must match its re-creation.
B. The misconfigured VM must be ‘deleted’ and re-created as a Generation 1 VM.

# Converting Generation 2 virtual disk to Gen 1
$diskFile='\\VIRTUALMACHINES\HyperV\originalDisk_gen2.vhdx'
$fixedFile='\\VIRTUALMACHINES\HyperV\originalDisk_gen1.vhdx'
Convert-VHD -Path $diskFile -DestinationPath $fixedFile -VHDType Dynamic

PowerShell: Find Guest VMs Associated with a Certain Storage Path

# findGuestMvsByStorage.ps1

$storagePath='\\SMBSERVER009'

function getAllGuestVms($clusterName){
  try{
    Import-Module FailoverClusters
    $clusterName=if($clusterName){
      $clustername
    }else{
      (get-cluster).name
    }
    $allHyperVHosts={(Get-ClusterNode -Cluster $clusterName|?{ $_.State -eq "Up" }).Name | %{$_.ToLower()}}.Invoke()
    $allVms=foreach ($hyperVHost in $allHyperVHosts){
      invoke-command -computername $hyperVHost -scriptblock{
        write-host "Getting VM List on $env:computername";
        Get-VM |select-object Name,State,Status,Path
      }|select-object * -ExcludeProperty RunspaceId,PSShowComputerName
    }
    if($allVms){return $allVms}else{return $null}
  }catch{
    write-warning $_
    return $false
  }
}
function findGuestMvsByStorage{
  param (
    $storagePath,
    $clusterName=$null
    )
  try{
      $allVms=getAllGuestVms $clusterName
      if(!$allVms){return $null}
      $matchedStorage=$allVms|?{$_.Path -like "*$storagePath*"}
      $online=$matchedStorage|?{$_.State.Value -like 'Running*'}
      $offline=$matchedStorage|?{$_.State.Value -notlike 'Running*'}
      if($online){
        write-host "There are $($online.count) online VMs associated with '$storagePath'"
      }
      if($offline){
        write-host "There are $($offline.count) offlined VMs associated with '$storagePath'"
      }
      if($matchedStorage){
        $matchedStorage
        return $matchedStorage
      }else{
        write-host "'$storagePath' is not being associated with any VM in cluster '$((get-cluster).Name)'"
        return $null
      }
  }catch{
      write-warning $_
      return $false
  }
}

$result=findGuestMvsByStorage $storagePath
$result|select Name,State,PSComputerName
# Sample Output
PS C:\Windows\system32> $result=findGuestMvsByStorage $storagePath
Getting VM List on HYV01
Getting VM List on HYV02
Getting VM List on HYV03
Getting VM List on HYV04
Getting VM List on HYV05
Getting VM List on HYV06
Getting VM List on HYV07
Getting VM List on HYV08
Getting VM List on HYV09
Getting VM List on HYV10
Getting VM List on HYV11
Getting VM List on HYV12
Getting VM List on HYV13
Getting VM List on HYV14
Getting VM List on HYV15
Getting VM List on HYV16
Getting VM List on HYV17
Getting VM List on HYV18
Getting VM List on HYV19
There are 6 online VMs associated with '\\FILESERVER009'
There are 16 offlined VMs associated with '\\FILESERVER009'

PS C:\Windows\system32> $result|select Name,State
Name                           State
----                           -----
TESTVM01                       OffCritical
TESTVM02                       Off
TESTVM03                       OffCritical
TESTVM04                       OffCritical
TESTVM05                       RunningCritical

PowerShell: Restart a Service on All Hyper-V Hosts of a Cluster

$serviceName='vmms'
$clusterName='HyperV-cluster001'

function restartServiceAllClusterNodes($service='vmms',$clusterName){
  function restartService($serviceName){
    $waitSeconds=40
    $isValidProcess=try{[bool](get-service $serviceName -EA Stop)}catch{$false}
    if($isValidProcess){
        try{
            $process=Start-Process -FilePath powershell.exe -ArgumentList "-Command Restart-Service $serviceName" -PassThru -NoNewWindow
            $process|Wait-Process -Timeout $waitSeconds -ErrorAction Stop
            return $true
        }catch{
            write-warning $_
            $process|Stop-Process -Force
            $processId=(get-process $serviceName).Id
            if($processId){
                write-host "Program now forcefully kills PID $processId of process $serviceName"
                $null=$processId|%{taskkill /f /pid $_} # works more reliably than Stop-Process $processName -Force
                Start-Service $serviceName -ErrorAction Ignore
                $started=$(try{get-service $serviceName}catch{$false})
                if($started){
                    write-host "'serviceName' status is now $($started.Status)"
                    return $true
                }else{
                    write-warning "'serviceName' status is $($started.Status)"
                    return $false
                }
            }else{
                write-warning "Service '$serviceName' PID not found."
                return $false        
            }
        }
    }
  }

  $results=@()
  try{
    Import-Module FailoverClusters
    $clusterName=if($clusterName){
      invoke-command -computername $clustername {(get-cluster).name}
    }else{
      (get-cluster).name
    }
    $allHyperVHosts={(Get-ClusterNode -Cluster $clusterName|?{ $_.State -eq "Up" }).Name | %{$_.ToLower()}}.Invoke()
    foreach ($hyperVHost in $allHyperVHosts){
      $result=invoke-command -computername $hyperVHost -EA SilentlyContinue -scriptblock {
        param($restartService,$serviceName)
        [scriptblock]::create($restartService).invoke($servicename)
      } -Args ${function:restartService},$serviceName
      write-host "$hypervHost=$result"
      $results+=[pscustomobject]@{$hypervHost=$result}
    }
  }catch{
    write-warning $_    
  }
}

$results=restartServiceAllClusterNodes $serviceName $clusterName
$results

PowerShell: Find Hyper-V Host by Guest VM Name

# findVmHostByGuestName.ps1

$vmName='TESTVM'

function findVmHostByGuestName($vmName){
    try{
        Import-Module Hyper-V
        Import-Module FailoverClusters
        $allHyperVHosts={(Get-ClusterNode | Where { $_.State -eq "Up" }).Name | %{$_.ToLower()}}.Invoke()
        $allVms=foreach ($hyperVHost in $allHyperVHosts){invoke-command -computername $hyperVHost -scriptblock{write-host "Getting VM List on $env:computername";Get-VM |select Name,Path}|select-object * -ExcludeProperty RunspaceId,PSShowComputerName}
        $matchedHost=$allVms|?{$_.Name -like "*$vmName*"}
        if($matchedHost){
            return $matchedHost
        }else{
            write-host "'$vmName' is not found in cluster '$((get-cluster).Name)'"
            return $null
        }
    }catch{
        write-warning $_
        return $false
    }
}

findVmHostByGuestName $vmName

PowerShell: Get All Hyper-V Host Spectre Patch Versions

# getAllVmSpectrePatchVersions.ps1

function getHyperVHostsInForest{
    function includeRSAT{
        $ErrorActionPreference='stop'
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        #$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
        $rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
        $rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
        $rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
        $rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
        $rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
   
        # This command does not work on Windows 2012R2
        #$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
        #Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
        #NT\CurrentVersion.
        #At line:1 char:2
        #+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        #    + CategoryInfo          : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
        #    + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
   
        $releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
        $osVersion=[System.Environment]::OSVersion.Version
        [double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)" 
        $osName=(Get-WmiObject Win32_OperatingSystem).Name
        #$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
        #    1 {'client'}
        #    2 {'domaincontroller'}
        #    3 {'memberserver'}
        #    }
   
        $windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
   
        switch ($releaseId){
            1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
            1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
            1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
        }
       
        switch ($osVersionMajorMinor){
            {$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
            {$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
            {$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
            {$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
        }
  
        if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
            Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
            try{
                # If OS is Windows Server, then install RSAT using a different method
                if ($osName -match "^Microsoft Windows Server") {
                    # This sequence has confirmed to be valid on Windows Server 2008 R2 and above
                    Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
                    Import-Module ServerManager
                    Add-WindowsFeature RSAT-AD-PowerShell
                    }
                else{
                    Write-Verbose "This sequence targets Windows Client versions"
                    $destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
                    Write-Host "Downloading RSAT from $link..."
                    Start-BitsTransfer -Source $link -Destination $destinationFile
                    $fileCheck=Get-AuthenticodeSignature $destinationFile
                    if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
                    $wusaCommand = $destinationFile + " /quiet"
                    Write-host "Installing RSAT - please wait..."
                    Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
                    }
                return $true
                }
            catch{
                write-warning "$($error[0].Exception)"
                return $false
                }
        }else{
            Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
            return $true
            }
    }
     function listAllHyperVNodes($verbose=$true){
        try{
            $timer=[System.Diagnostics.Stopwatch]::StartNew()
            $domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}
            foreach ($domain in $domains){
                #[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
                write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
                $allClusters=(get-cluster -domain $domain).Name
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: cluster names collected"
                    }

                $allHyperVNodes=@()
                foreach ($cluster in $allClusters){
                    $nodes=.{$x=Get-ClusterNode -Cluster $cluster -ea SilentlyContinue
                            if($x){
                                $x|Where-Object{$_.State -eq 'Up'}|Select-Object Name,@{name='Cluster';e={$cluster}}
                            }else{
                                $false
                            }
                            }
                    if($nodes){$allHyperVNodes+=$nodes}
                }
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: Hyper Node names collected..."
                    }
                }                
            return $allHyperVNodes
        }catch{
            Write-Error $_
            return $false
            }
    }
 
    try{
        $null=includeRSAT;
        $hyperVHosts=listAllHyperVNodes
        #$hyperVHostNames=sortArrayStringAsNumbers $hyperVHosts
        $hyperVHostNames=$hyperVHosts|sort -property Cluster
        return $hyperVHostNames
    }catch{
        Write-Error $_
        return $false
        }
    }

function pickList($list){
    # Although it's more efficient to obtain the index and set it as display,
    # humans prefer see a list that starts with 1, instead of 0
    $display=for ($i=0;$i -lt $list.count;$i++){
        "$($i+1)`:`t$($list[$i])`r`n";
        }
    $lines=($display | Measure-Object -Line).Lines
    write-host $($display)
    $maxAttempts=3
    $attempts=0;
    while ($attempts -le $maxAttempts){
        if($attempts++ -ge $maxAttempts){
            write-host "Attempt number $maxAttempts of $maxAttempts. Exiting loop..`r`n"
            break;
            }
        $userInput = Read-Host -Prompt 'Please pick a number from the list above';
        try {
            $value=[int]$userInput;
            }catch{
                $value=-1;
                }
        if ($value -lt 1 -OR $value -gt $lines){
            cls;
            write-host "Attempt number $attempts of $maxAttempts`: $userInput is an invalid value. Try again..`r`n"
            write-host $display
            }else{
                $item=$list[$value-1];
                write-host "$userInput corresponds to $item`r`n";
                return $item
                }
        }     
    }
function selectCluster($clusters){
    # Requires function named pickList
    $uniqueClusters=$clusters|select -unique
    $pickedCluster=$(pickList $uniqueClusters)
    if($pickedCluster){
        return $pickedCluster
    }else{
        write-warning 'No clusternames were picked.'
        return $false
    }
}

function pickHost($hosts){
    # Requires function named pickList
    $pickedHost=$(pickList $hosts)
    if($pickedHost){
        return $pickedHost
    }else{
        write-warning 'No clusternames were picked.'
        return $false
    }
}
function sortArrayStringAsNumbers([string[]]$names){
    $hashTable=@{}
    foreach ($name in $names){
        #[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
        #$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
        $x=.{[void]($name -match '(?:.(\d+)+)$');($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft(8,'0')}
        $hashTable.Add($name,$x)
        }
    $sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
    return $sorted
}

write-host "Obtaining cluster names and associated hosts..."
$hyperVHostsInForest=getHyperVHostsInForest
$pickedCluster=selectCluster $hyperVHostsInForest.Cluster
$pickedHosts=$hyperVHostsInForest|?{$_.Cluster -eq $pickedCluster}
$pickedHyperVHosts=sortArrayStringAsNumbers $pickedHosts.Name

function getSpectrePatchingVersions($hyperVHosts){
    write-host "Now obtaining Spectre Patching versions of Hyper-V Hosts..."
    $results=@{}
    foreach ($hyperVHost in $hyperVHosts){
        $spectrePatchingVersion=(Get-WmiObject -ComputerName $hyperVHost -ClassName Win32_BIOS).SMBIOSBIOSVersion
        write-host "$hyperVHost`: $spectrePatchingVersion"
        $results+=@{$hyperVHost=$spectrePatchingVersion}
    }
    return $results
}
getSpectrePatchingVersions $pickedHyperVHosts
function setVmMigrationPerformanceRemote($hyperVHosts){
    function setVmMigrationPerformance{
        param($performanceOption='TCPIP') # TCP is most compatible, and the other options are SMB and Compression
        $ErrorActionPreference='stop'
        try{
            Set-VMHost -VirtualMachineMigrationPerformanceOption $performanceOption
            return $true
        }catch{
            return $false
        }
    }
    $results=@{}
    $performanceOption='TCPIP'
    foreach ($hyperVHost in $hyperVHosts){
        try{
            $result=invoke-command -ComputerName $hyperVHost -ScriptBlock {
                param($setVmMigrationPerformance,$performanceOption)
                [scriptblock]::create($setVmMigrationPerformance).invoke($performanceOption)
            } -Args ${function:setVmMigrationPerformance},$performanceOption
            if($result){
                write-host "$hyperVHost`: $performanceOption"
                $results+=@{$hyperVHost=$performanceOption}
            }
        }catch{
            write-warning $_
        }
    }
    return $results
}
setVmMigrationPerformanceRemote $pickedHyperVHosts

PowerShell: Fix All VMs CPU Compatibility Setting

# fixAllVmCpuCompatibility.ps1
# version 0.01
function getHyperVHostsInForest{
    function includeRSAT{
        $ErrorActionPreference='stop'
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        #$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
        $rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
        $rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
        $rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
        $rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
        $rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
   
        # This command does not work on Windows 2012R2
        #$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
        #Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
        #NT\CurrentVersion.
        #At line:1 char:2
        #+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        #    + CategoryInfo          : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
        #    + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
   
        $releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
        $osVersion=[System.Environment]::OSVersion.Version
        [double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)" 
        $osName=(Get-WmiObject Win32_OperatingSystem).Name
        #$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
        #    1 {'client'}
        #    2 {'domaincontroller'}
        #    3 {'memberserver'}
        #    }
   
        $windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
   
        switch ($releaseId){
            1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
            1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
            1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
        }
       
        switch ($osVersionMajorMinor){
            {$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
            {$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
            {$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
            {$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
        }
  
        if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
            Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
            try{
                # If OS is Windows Server, then install RSAT using a different method
                if ($osName -match "^Microsoft Windows Server") {
                    # This sequence has confirmed to be valid on Windows Server 2008 R2 and above
                    Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
                    Import-Module ServerManager
                    Add-WindowsFeature RSAT-AD-PowerShell
                    }
                else{
                    Write-Verbose "This sequence targets Windows Client versions"
                    $destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
                    Write-Host "Downloading RSAT from $link..."
                    Start-BitsTransfer -Source $link -Destination $destinationFile
                    $fileCheck=Get-AuthenticodeSignature $destinationFile
                    if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
                    $wusaCommand = $destinationFile + " /quiet"
                    Write-host "Installing RSAT - please wait..."
                    Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
                    }
                return $true
                }
            catch{
                write-warning "$($error[0].Exception)"
                return $false
                }
        }else{
            Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
            return $true
            }
    }
     function listAllHyperVNodes($verbose=$true){
        try{
            $timer=[System.Diagnostics.Stopwatch]::StartNew()
            $domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}
            foreach ($domain in $domains){
                #[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
                write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
                $allClusters=(get-cluster -domain $domain).Name
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: cluster names collected"
                    }

                $allHyperVNodes=@()
                foreach ($cluster in $allClusters){
                    $nodes=.{$x=Get-ClusterNode -Cluster $cluster -ea SilentlyContinue
                            if($x){
                                $x|Where-Object{$_.State -eq 'Up'}|Select-Object Name,@{name='Cluster';e={$cluster}}
                            }else{
                                $false
                            }
                            }
                    if($nodes){$allHyperVNodes+=$nodes}
                }
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: Hyper Node names collected..."
                    }
                }                
            return $allHyperVNodes
        }catch{
            Write-Error $_
            return $false
            }
    }
 
    try{
        $null=includeRSAT;
        $hyperVHosts=listAllHyperVNodes
        #$hyperVHostNames=sortArrayStringAsNumbers $hyperVHosts
        $hyperVHostNames=$hyperVHosts|sort -property Cluster
        return $hyperVHostNames
    }catch{
        Write-Error $_
        return $false
        }
    }

function pickList($list){
    # Although it's more efficient to obtain the index and set it as display,
    # humans prefer see a list that starts with 1, instead of 0
    $display=for ($i=0;$i -lt $list.count;$i++){
        "$($i+1)`:`t$($list[$i])`r`n";
        }
    $lines=($display | Measure-Object -Line).Lines
    write-host $($display)
    $maxAttempts=3
    $attempts=0;
    while ($attempts -le $maxAttempts){
        if($attempts++ -ge $maxAttempts){
            write-host "Attempt number $maxAttempts of $maxAttempts. Exiting loop..`r`n"
            break;
            }
        $userInput = Read-Host -Prompt 'Please pick a number from the list above';
        try {
            $value=[int]$userInput;
            }catch{
                $value=-1;
                }
        if ($value -lt 1 -OR $value -gt $lines){
            cls;
            write-host "Attempt number $attempts of $maxAttempts`: $userInput is an invalid value. Try again..`r`n"
            write-host $display
            }else{
                $item=$list[$value-1];
                write-host "$userInput corresponds to $item`r`n";
                return $item
                }
        }     
    }
function selectCluster($clusters){
    # Requires function named pickList
    $uniqueClusters=$clusters|select -unique
    $pickedCluster=$(pickList $uniqueClusters)
    if($pickedCluster){
        return $pickedCluster
    }else{
        write-warning 'No clusternames were picked.'
        return $false
    }
}

function pickHost($hosts){
    # Requires function named pickList
    $pickedHost=$(pickList $hosts)
    if($pickedHost){
        return $pickedHost
    }else{
        write-warning 'No clusternames were picked.'
        return $false
    }
}
function sortArrayStringAsNumbers([string[]]$names){
    $hashTable=@{}
    foreach ($name in $names){
        #[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
        #$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
        $x=.{[void]($name -match '(?:.(\d+)+)$');($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft(8,'0')}
        $hashTable.Add($name,$x)
        }
    $sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
    return $sorted
}

function enableCpuCompatibility($vmName){
    $compatibilityForMigration=(Get-VMProcessor $vmName).CompatibilityForMigrationEnabled
    if(!$compatibilityForMigration){    
        $vmIsRunning=(get-vm $vmname).State -eq 'Running'
        if($vmIsRunning){stop-vm $vmName}
        Set-VMProcessor $vmName -CompatibilityForMigrationEnabled 1
        if($vmIsRunning){start-vm $vmName}
    }else{
        write-host "$vmName already has CPU CompatibilityForMigrationEnabled set to True"
    }
}

write-host "Obtaining cluster names and associated hosts..."
$hyperVHostsInForest=getHyperVHostsInForest
$pickedCluster=selectCluster $hyperVHostsInForest.Cluster
$pickedHosts=$hyperVHostsInForest|?{$_.Cluster -eq $pickedCluster}
$pickedHyperVHosts=sortArrayStringAsNumbers $pickedHosts.Name
$results=@()
foreach ($hyperVHost in $pickedHyperVHosts){
    write-host "Performing discovery on $hyperVhost..."
    $getVMCpuCompatiblity={
        Get-VMProcessor *|Select-Object VMName,CompatibilityForMigrationEnabled,@{name='Online';e={if((get-vm $_.VMName).State -eq 'Running'){$true}else{$false}}}
    }
    $result=invoke-command -ComputerName $hyperVHost -ScriptBlock $getVMCpuCompatiblity|Select-Object -Property * -ExcludeProperty RunspaceId
    write-host ($result|out-string).trim()
    $results+=$result
}
$negatives=$results|?{$_.CompatibilityForMigrationEnabled -eq $false -and $_.Online -eq $true}

# Fix all items
foreach ($item in $negatives){
    $vmHost=$item.PSComputerName
    $vmName=$item.VMName
    invoke-command -computername $vmHost -scriptblock {
        param($enableCpuCompatibility,$vmName)
        [scriptblock]::create($enableCpuCompatibility).invoke($vmName)
    } -Args ${function:enableCpuCompatibility},$vmName
}
# Fix each item
$vmName='TESTVM008'
$vmHost=($negatives|?{$_.VMName -eq $vmName}).PSComputerName
if($vmHost){
    invoke-command -computername $vmHost -scriptblock {
        param($vmName)$vmIsRunning=(get-vm $vmname).State -eq 'Running'
        if($vmIsRunning){stop-vm $vmName -Force}
        Set-VMProcessor $vmName -CompatibilityForMigrationEnabled 1
        if($vmIsRunning){start-vm $vmName}
    } -Args $vmName
}else{
    write-host "VMName $vmName doesn't match an existing record."
}

PowerShell: Search for Hyper-V Guest VM That Has Not Been Registered In Cluster

# findVmHost.ps1

$vmName='TESTVM01'

function findVmHost($vmName){
    try{
        Import-Module Hyper-V
        Import-Module FailoverClusters
        $allHyperVHosts={(Get-ClusterNode | Where { $_.State –eq "Up" }).Name | %{$_.ToLower()}}.Invoke()
        $allVms=foreach ($hyperVHost in $allHyperVHosts){invoke-command -computername $hyperVHost -scriptblock{write-host "Getting VM List on $env:computername";Get-VM |select Name,Path}|select-object * -ExcludeProperty RunspaceId,PSShowComputerName}
        $matchedHost=$allVms|?{$_.Name -like "*$vmName*"}
        if($matchedHost){
            return $matchedHost
        }else{
            write-host "'$vmName' is not found in cluster '$((get-cluster).Name)'"
            return $null
        }
    }catch{
        write-warning $_
        return $false
    }
}

findVmHost $vmName

How To Stop, Start, Restart a Windows Service Being Stuck in ‘Stopping’ Status

Convenient Function:

function restartService($serviceName){
    $waitSeconds=40
    $isValidProcess=try{[bool](get-service $serviceName -EA Stop)}catch{$false}
    if($isValidProcess){
        try{
            $process=Start-Process -FilePath powershell.exe -ArgumentList "-Command Restart-Service $serviceName" -PassThru -NoNewWindow
            $process|Wait-Process -Timeout $waitSeconds -ErrorAction Stop
            return $true
        }catch{
            write-warning $_
            $process|Stop-Process -Force
            $processId=(get-process $serviceName).Id
            if($processId){
                write-host "Program now forcefully kills PID $processId of process $serviceName"
                $null=$processId|%{taskkill /f /pid $_} # works more reliably than Stop-Process $processName -Force
                Start-Service $serviceName -ErrorAction Ignore
                $started=$(try{get-service $serviceName}catch{$false})
                if($started){
                    write-host "'serviceName' status is now $($started.Status)"
                    return $true
                }else{
                    write-warning "'serviceName' status is $($started.Status)"
                    return $false
                }
            }else{
                write-warning "Service '$serviceName' PID not found."
                return $false         
            }
        }
    }
}
 
restartService vmms
Symptom:
# Example error of Hyper-V Virtual Machine Management Service
Service 'Hyper-V Virtual Machine Management (vmms)' cannot be stopped due to the following error: Cannot stop vmms
service on computer '.'.
+ CategoryInfo : CloseError: (System.ServiceProcess.ServiceController:ServiceController) [Restart-Service], ServiceCommandException
+ FullyQualifiedErrorId : CouldNotStopService,Microsoft.PowerShell.Commands.RestartServiceCommand
+ PSComputerName : HYPERV05
Resolution:
# Kill the process id prior to attempting to start the service
$serviceName='vmms'
$processId=(get-process $serviceName).Id
$null=taskkill /f /pid $processId
Start-Service $serviceName
PS C:\Windows\system32> taskkill /f /pid $processId
SUCCESS: The process with PID 21404 has been terminated.
PS C:\Windows\system32> restart-service vmms
WARNING: Waiting for service 'Hyper-V Virtual Machine Management (vmms)' to stop...
WARNING: Waiting for service 'Hyper-V Virtual Machine Management (vmms)' to stop...
WARNING: Waiting for service 'Hyper-V Virtual Machine Management (vmms)' to stop...
WARNING: Waiting for service 'Hyper-V Virtual Machine Management (vmms)' to stop...
WARNING: Waiting for service 'Hyper-V Virtual Machine Management (vmms)' to stop...

PowerShell: Copy Active Directory User

# copyADUser.ps1
# Version 0.01

$fromUsername='mTyson'
$newUserFirstname='Bruce'
$newUserLastname='Lee'
$newPassword='SOMEPASSWORD'
$newEmailAddress='bruce.lee@kimconnect.com'
$setProxyAddress=$false

function copyADUser{
  param(
    $fromUsername,
    $newUserFirstname,
    $newUserLastname,
    $newPassword,
    $newEmailAddress,
    $setProxyAddress=$false
  )
  try{
    Import-Module activedirectory
  }catch{
    write-warning $_
    return $false
  }
  $availableUserName=.{
    $index=0
    $x=($newUserFirstname.toLower())[$index]+$newUserLastname.tolower()
    do{
      $usernameExists=try{$null=get-aduser $x;$true}catch{$false}
      if($usernameExists){
        $index+=1
        $x=(($newUserFirstname.toLower())[0..$index] -join '')+$newUserLastname.tolower()
      }else{
        return $x
      }
    }until(!$usernameExists -or $newUserFirstname.length+$newUserLastname.length -eq $x.length)
    $number=1
    $x=($newUserFirstname.toLower())[$index]+$newUserLastname.tolower()
    do{
      $y=$x+"$number"
      $usernameExists=try{$null=get-aduser $y;$true}catch{$false}
      if($usernameExists){
        $number+=1
        $y=$x+"$number"
      }else{
        return $y
      }      
    }until($number -ge 10000)
    return $null
  }
  if(!$availableUserName){
    write-warning "Unable to proceed due to username being NOT available."
    return $false
  }
  function copyGroupMemberships($fromIdentity,$toIdentity){
    $ErrorActionPreference='stop'
    try{
      Import-module activedirectory
      $groupNames=(Get-ADPrincipalGroupMembership $fromIdentity).Name
      $groupNames|%{try{Add-ADGroupMember -Identity "$_" -Members $toIdentity -ea SilentlyContinue}catch{}}
      $currentMemberships=(Get-ADPrincipalGroupMembership $toIdentity).Name
      write-host "User '$env:USERDOMAIN\$toIdentity' now has these memberships:`r`n---------------------------------`r`n$($currentMemberships|out-string)"
      return $true
    }catch{
      write-warning $_
      return $false
    }
  }

  $fullName=$newUserFirstname+' '+$newUserLastname
  $newPrinciplename=$availableUserName+'@'+$env:USERDNSDOMAIN
  $securedPass=(ConvertTo-SecureString $newPassword -AsPlainText -Force)
  write-host "Creating UserID '$availableUserName' with full name of '$fullname', and setting the password of '$newPassword'"
  try{
    $copyAttributes=Get-ADUser -Identity $fromUsername -Properties StreetAddress,City,Title,PostalCode,Office,Department,Manager
    $targetOu=.{
      $x=((Get-aduser $fromUsername).DistinguishedName -split ',')
      return $x[1..$x.length] -join ','
    }
    New-ADUser -SAMAccountName $availableUserName -Name $fullName -GivenName $newUserFirstname -Surname $newUserLastname -Instance $copyAttributes -DisplayName $fullName -UserPrincipalName $newPrincipleName -AccountPassword $securedPass -ChangePasswordAtLogon $false -Enabled $true
    Set-ADUser -Identity $availableUserName -EmailAddress $newEmailAddress
    if($setProxyAddress){
      Set-ADUser -Identity $availableUserName -Add @{proxyAddresses="SMTP:$newEmailAddress";proxyAddressesForGapps="SMTP:$newEmailAddress"}
    }

    # This error would occur if UserPrincipleName is not specified or having conflicts
    # New-ADUser : The operation failed because UPN value provided for addition/modification is not unique forest-wide
    # At line:1 char:5
    # +     New-ADUser -Name $fullName -GivenName $newUserFirstname -Surname  ...
    # +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #     + CategoryInfo          : NotSpecified: (CN=Bruce Lee...DC=kimconnect,DC=com:String) [New-ADUser], ADException
    #     + FullyQualifiedErrorId : ActiveDirectoryServer:8648,Microsoft.ActiveDirectory.Management.Commands.NewADUser

    $currentIdentity=(Get-aduser $availableUserName).DistinguishedName
    Move-ADObject -Identity $currentIdentity -TargetPath $targetOu
    copyGroupMemberships $fromUsername $availableUserName
    return $true
  }catch{
    write-warning $_
    return $false
  }
}

copyADUser $fromUsername `
  $newUserFirstname `
  $newUserLastname `
  $newPassword `
  $newEmailAddress `
  $setProxyAddress

PowerShell: Quickly Add Users into Groups on a List of Computers

$newVmNames=@(
    'SERVER0001', 
    'SERVER0002'
)
$newUsers=@(
    'domain\user1'
)
$groupNames='Administrators','Remote Desktop Users'

$newVmNames|%{
    invoke-command -computername $_ {
        param($newUsers,$groupNames)
        try{            
            $groupNames|%{
                Add-LocalGroupMember -Group $_ -Member $newUsers
                write-host "$env:computername`: Group name '$groupName' now has these users`r`n$(get-localgroupmember $groupName)"
            }
        }catch{
            write-warning $_
        }        
    } -ArgumentList $newUsers,$groupNames
}

PowerShell: How To Disable and/or Stop Windows Service

Disable and Stop Using Native Command ‘Set-Service’

# To disable service as well as stopping it, assuming that the service does not have dependencies
$serviceName='windows_exporter'
$computernames=@('SERVER02','SERVER03')
$setStatus='stopped'
$setStartup='disabled'

Set-Service -Name $serviceName -Status $setStatus -StartupType $setStartup -computername $computernames

# Possible error: this means the service cannot be disabled without disabling its upstream parent services
Set-Service : Cannot stop service 'windows_exporter (windows_exporter)' because it is dependent on other services.
At line:1 char:1
+ Set-Service -Name $serviceName -Status stopped -StartupType disabled  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.ServiceProcess.ServiceController:ServiceController) [Set-Service], ServiceCommandException
    + FullyQualifiedErrorId : ServiceIsDependentOnNoForce,Microsoft.PowerShell.Commands.SetServiceCommand

Using SC

$computernames=@('SERVER01','SERVER02')
$serviceName='windows_exporter'
$action='start' # or stop

function setService{
    param(
        $computernames=$env:computername,
        $serviceName='RemoteRegistry',
        $action='start' # or stop
    )
    $computernames|%{Start-Process "$env:WINDIR\system32\sc.exe" \\$_,start,$serviceName -NoNewWindow -Wait}
}

setService $computerNames $serviceName $action
# To simply stop or start a service using the legacy tool SC.EXE on a list of servers
$computerNames=$env:computername
$serviceName='windows_exporter'
$action='start' # or stop
$computernames|%{Start-Process "$env:WINDIR\system32\sc.exe" \\$_,$action,$serviceName -NoNewWindow -Wait}

# Note: The native stop-service Powershell command does not have a computername parameter; Hence, the native command above is used as a workaround
Stop-Service : A parameter cannot be found that matches parameter name 'computername'.
At line:1 char:14
+ stop-service -computername $computernames -name $servicename -force
+              ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Stop-Service], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.StopServiceCommand
# Sample output
SERVICE_NAME: windows_exporter
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 3  STOP_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

Verify

PS C:\Users\administrator> get-service windows_exporter -computername $computernames|select MachineName,Name,Status

MachineName   Name              Status
-----------   ----              ------
dev01 windows_exporter Stopped
dev02  windows_exporter Stopped
dev03   windows_exporter Stopped
dev04   windows_exporter Stopped

CSS Snippet to Target Phones, Tablets, and Desktops Separately

@media (min-width:20em)  { /* iPhones, Androids portrait 480x320 phones */ }
@media (min-width:30em)  { /* e-readers (Nook/Kindle), mini tablets 600x600 */ }
@media (min-width:40em)  { /* medium tablets, iPads, and e-readers, landscape 800x480 */ }
@media (min-width:60em)  { /* standard tablets, landscape iPads, lo-resolution laptops ands desktops */ }
@media (min-width:64em) { /* larger tablets, standard laptops, and 24" monitors */ }
@media (min-width:80em) { /* High resolution laptops and desktops */ }

PowerShell: Technique to Pass Array Variable as Argument

Problem:

The subtle difference between invoke-command -scriptblock {} -Args vs -ArgumentList is elusive enough to waste a coder’s time trying to understand why a forced casting of an Array type variable doesn’t behave as expected. Here’s an illustration for Google searchers who has stumbled upon this puzzle.

The Correct Method of Passing Array into Invoke-Command

$accountsToAdd='intranet\user1','intranet\user2'
$localgroup='Remote Desktop Users'

# Check on whether certain accounts exist in a local group
invoke-command -scriptblock{
            param($principleNames,$groupName)
            $results=@()
            $currentMembers=try{
                (get-localgroupmember $groupName -ea stop).Name
            }catch{
                $x=net localgroup $groupName
                $x[6..$($x.length-3)]
            }
            write-host "Current members of $groupName`:`r`n$($currentMembers|out-string)"
            foreach($principle in $principleNames){
                $results+=[pscustomobject]@{
                    computername=$env:computername
                    groupname=$groupName
                    userName=$principle
                    usernameIsMember=[bool]($principle -in $currentMembers)
                }
            }
            return $results
        } -ArgumentList $accountsToAdd,$localGroup

The Incorrect Method of Passing Array into Invoke-Command

$accountsToAdd='intranet\user1','intranet\user2'
$localgroup='Remote Desktop Users'

# Check on whether certain accounts exist in a local group
invoke-command -scriptblock{
            param($principleNames,$groupName)
            $results=@()
            $currentMembers=try{
                (get-localgroupmember $groupName -ea stop).Name
            }catch{
                $x=net localgroup $groupName
                $x[6..$($x.length-3)]
            }
            write-host "Current members of $groupName`:`r`n$($currentMembers|out-string)"
            foreach($principle in $principleNames){
                $results+=[pscustomobject]@{
                    computername=$env:computername
                    groupname=$groupName
                    userName=$principle
                    usernameIsMember=[bool]($principle -in $currentMembers)
                }
            }
            return $results
        } -Args (,$accountsToAdd),$localGroup
# Sample Output:

WARNING: Cannot convert 'intranet\user1 intranet\user2' to the type 'Microsoft.PowerShell.Commands.LocalPrincipal' required by parameter 'Member'. Specified method is not supported.
Failed to compare two elements in the array.

PowerShell: Add New Virtual Disk to Existing Guest VM in Hyper-V

# Adding disks (optional)
$newVMNames='TestWindows2019'
$extraDiskSize='200GB'

if($extraDiskSize){
    foreach($newVmName in $newVMNames){
        STOP-VM -vmname $newVmName
        $diskFile=(join-path $destinationFolder $newVmName) + "\$newVmName`_disk1.vhdx"
        NEW-VHD -Fixed $diskFile -SizeBytes (Invoke-Expression $extraDiskSize) -ea Stop
        # Preempt error by adding (Invoke-Expression $sizeBytes)
        # New-VHD : Cannot bind parameter 'SizeBytes'. Cannot convert value "200GB" to type "System.UInt64". Error: "Input
        # string was not in a correct format."
        # At line:1 char:45
        # +         NEW-VHD -Fixed $diskFile -SizeBytes $extraDiskSize -ea Stop
        # +                                             ~~~~~~~~~~~~~~
        #     + CategoryInfo          : InvalidArgument: (:) [New-VHD], ParameterBindingException
        #     + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.Vhd.PowerShell.Cmdlets.NewVhd
        Add-VMHardDiskDrive -VMName $newVmName -Path $diskFile
    }
}