Public/SUBnet192.Toolset.ps1

#Requires -Modules ActiveDirectory

########################################################################################################################
# Get Installed Software
########################################################################################################################

Function Get-InstalledSoftware
{
   Param([String[]]$Computers)
   If (!$Computers) { $Computers = $ENV:ComputerName }
   $Base = New-Object PSObject;
   $Base | Add-Member Noteproperty ComputerName -Value $Null;
   $Base | Add-Member Noteproperty Name -Value $Null;
   $Base | Add-Member Noteproperty Publisher -Value $Null;
   $Base | Add-Member Noteproperty InstallDate -Value $Null;
   $Base | Add-Member Noteproperty EstimatedSize -Value $Null;
   $Base | Add-Member Noteproperty Version -Value $Null;
   $Base | Add-Member Noteproperty Wow6432Node -Value $Null;
   $Results = New-Object System.Collections.Generic.List[System.Object];

   foreach ($ComputerName in $Computers)
   {
      $Registry = $Null;
      Try { $Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $ComputerName); }
      Catch { Write-Output -ForegroundColor Red "$($_.Exception.Message)"; }

      If ($Registry)
      {
         $UninstallKeys = $Null;
         $SubKey = $Null;
         $UninstallKeys = $Registry.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall", $False);
         $UninstallKeys.GetSubKeyNames() | ForEach-Object {
            $SubKey = $UninstallKeys.OpenSubKey($_, $False);
            $DisplayName = $SubKey.GetValue("DisplayName");
            If ($DisplayName.Length -gt 0)
            {
               $Entry = $Base | Select-Object *
               $Entry.ComputerName = $ComputerName;
               $Entry.Name = $DisplayName.Trim();
               $Entry.Publisher = $SubKey.GetValue("Publisher");
               [ref]$ParsedInstallDate = Get-Date
               If ([DateTime]::TryParseExact($SubKey.GetValue("InstallDate"), "yyyyMMdd", $Null, [System.Globalization.DateTimeStyles]::None, $ParsedInstallDate))
               {
                  $Entry.InstallDate = $ParsedInstallDate.Value
               }
               $Entry.EstimatedSize = [Math]::Round($SubKey.GetValue("EstimatedSize") / 1KB, 1);
               $Entry.Version = $SubKey.GetValue("DisplayVersion");
               [Void]$Results.Add($Entry);
            }
         }

         If ([IntPtr]::Size -eq 8)
         {
            $UninstallKeysWow6432Node = $Null;
            $SubKeyWow6432Node = $Null;
            $UninstallKeysWow6432Node = $Registry.OpenSubKey("Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", $False);
            If ($UninstallKeysWow6432Node)
            {
               $UninstallKeysWow6432Node.GetSubKeyNames() | ForEach-Object {
                  $SubKeyWow6432Node = $UninstallKeysWow6432Node.OpenSubKey($_, $False);
                  $DisplayName = $SubKeyWow6432Node.GetValue("DisplayName");
                  If ($DisplayName.Length -gt 0)
                  {
                     $Entry = $Base | Select-Object *
                     $Entry.ComputerName = $ComputerName;
                     $Entry.Name = $DisplayName.Trim();
                     $Entry.Publisher = $SubKeyWow6432Node.GetValue("Publisher");
                     [ref]$ParsedInstallDate = Get-Date
                     If ([DateTime]::TryParseExact($SubKeyWow6432Node.GetValue("InstallDate"), "yyyyMMdd", $Null, [System.Globalization.DateTimeStyles]::None, $ParsedInstallDate))
                     {
                        $Entry.InstallDate = $ParsedInstallDate.Value
                     }
                     $Entry.EstimatedSize = [Math]::Round($SubKeyWow6432Node.GetValue("EstimatedSize") / 1KB, 1);
                     $Entry.Version = $SubKeyWow6432Node.GetValue("DisplayVersion");
                     $Entry.Wow6432Node = $True;
                     [Void]$Results.Add($Entry);
                  }
               }
            }
         }
      }
   }
   $Results
} #END function

########################################################################################################################
# Find Active RDP Sessions
########################################################################################################################

function Find-ActiveRDPSession
{
   <#
   .SYNOPSIS
   List all active RDP sessions on servers
 
   .DESCRIPTION
   List all active RDP sessions on servers (requires domain admin privileges)
 
   .NOTES
   Version : 1.0
   Author : Marc Bouchard
   e-Mail : marc@subnet192.com
   Twitter : @SUBnet192
 
   Requires ActiveDirectory module (run from computer with RSAT installed)
 
   .EXAMPLE
   Find-ActiveRDPSessions
   #>


   # Query Active Directory for computers running a Server operating system
   $Servers = Get-ADComputer -Filter { OperatingSystem -like "*server*" }

   # Loop through the list to query each server for login sessions
   ForEach ($Server in $Servers)
   {
      $ServerName = $Server.Name

      Write-Output "Querying $ServerName"

      # Run the qwinsta.exe and parse the output
      $queryResults = (qwinsta /server:$ServerName | ForEach-Object { (($_.trim() -replace "\s+", ",")) } | ConvertFrom-Csv)

      # Pull the session information from each instance
      ForEach ($queryResult in $queryResults)
      {
         $RDPUser = $queryResult.USERNAME
         $sessionType = $queryResult.SESSIONNAME

         # We only want to display where a "person" is logged in. Otherwise unused sessions show up as USERNAME as a number
         If (($RDPUser -match "[a-z]") -and ($NULL -ne $RDPUser))
         {
            Write-Output "$ServerName logged in by $RDPUser on $sessionType"
         }
      }
   }
} #END function

########################################################################################################################
#
########################################################################################################################

Function Get-Uptime
{
   <#
   .SYNOPSIS
   Shows uptime information for a computer
 
   .DESCRIPTION
   Shows uptime information for a computer
 
   .PARAMETER ComputerName
   Specify the computer name to check
 
   .EXAMPLE
   PS C:\> get-adcomputer -Filter {Name -like "WKS*"} | ForEach-Object {Get-Uptime $_.Name}
   #>

   [CmdletBinding()]

   Param (
      [Parameter(
         ValueFromPipeline = $true,
         ValueFromPipelineByPropertyName = $true,
         Position = 0)]

      [string[]]
      $ComputerName = $env:COMPUTERNAME,

      [Switch]
      $ShowOfflineComputers

   )

   BEGIN
   {
      $ErroredComputers = @()
   }

   PROCESS
   {
      Write-Output "Processing $ComputerName"
      Foreach ($Computer in $ComputerName)
      {
         Try
         {
            $OS = Get-CimInstance Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
            $LastBootUpTime = $OS.LastBootUpTime
            $Uptime = (Get-Date) - $LastBootUpTime
            $Properties = @{ComputerName = $Computer
               LastBoot                  = $LastBootUpTime
               Uptime                    = ([String]$Uptime.Days + " Days " + $Uptime.Hours + " Hours " + $Uptime.Minutes + " Minutes")
            }

            $Object = New-Object -TypeName PSObject -Property $Properties | Select-Object ComputerName, LastBoot, UpTime

         }
         catch
         {
            if ($ShowOfflineComputers)
            {
               $ErrorMessage = $Computer + " Error: " + $_.Exception.Message
               $ErroredComputers += $ErrorMessage

               $Properties = @{ComputerName = $Computer
                  LastBoot                  = "Unable to Connect"
                  Uptime                    = "Error Shown Below"
               }

               $Object = New-Object -TypeName PSObject -Property $Properties | Select-Object ComputerName, LastBoot, UpTime
            }

         }
         finally
         {
            Write-Output $Object

            $Object = $null
            $OS = $null
            $Uptime = $null
            $ErrorMessage = $null
            $Properties = $null
         }
      }

      if ($ShowOfflineComputers)
      {
         Write-Output ""
         Write-Output "Errors for Computers not able to connect."
         Write-Output $ErroredComputers
      }
   }
   END { }
} # END Function

########################################################################################################################
#
########################################################################################################################

function Ping-IPRange
{
   <#
   .SYNOPSIS
      Sends ICMP echo request packets to a range of IPv4 addresses between two given addresses.
 
   .DESCRIPTION
         This function lets you sends ICMP echo request packets ("pings") to
         a range of IPv4 addresses using an asynchronous method.
 
         Therefore this technique is very fast but comes with a warning.
         Ping sweeping a large subnet or network with many swithes may result in
         a peak of broadcast traffic.
         Use the -Interval parameter to adjust the time between each ping request.
         For example, an interval of 60 milliseconds is suitable for wireless networks.
         The RawOutput parameter switches the output to an unformated
         [System.Net.NetworkInformation.PingReply[]].
 
   .INPUTS
      None
      You cannot pipe input to this funcion.
 
   .OUTPUTS
      The function only returns output from successful pings.
 
      Type: System.Net.NetworkInformation.PingReply
 
      The RawOutput parameter switches the output to an unformated
      [System.Net.NetworkInformation.PingReply[]].
 
   .EXAMPLE
      Ping-IPRange -StartAddress 192.168.1.1 -EndAddress 192.168.1.254 -Interval 20
 
      IPAddress Bytes Ttl ResponseTime
      --------- ----- --- ------------
      192.168.1.41 32 64 371
      192.168.1.57 32 128 0
      192.168.1.64 32 128 1
      192.168.1.63 32 64 88
      192.168.1.254 32 64 0
 
      In this example all the ip addresses between 192.168.1.1 and 192.168.1.254 are pinged using
      a 20 millisecond interval between each request.
      All the addresses that reply the ping request are listed.
   #>


   [CmdletBinding(ConfirmImpact = 'Low')]
   Param(
      [parameter(Mandatory = $true, Position = 0)] [System.Net.IPAddress]$StartAddress,
      [parameter(Mandatory = $true, Position = 1)] [System.Net.IPAddress]$EndAddress,
      [int]$Interval = 30,
      [Switch]$RawOutput = $false
   )

   function New-IPRange
   {
      [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
      [OutputType([System.Net.IPAddress])]
      param($start, $end)

      [byte[]]$BySt = $start.GetAddressBytes()
      [Array]::Reverse($BySt)
      [byte[]]$ByEn = $end.GetAddressBytes()
      [Array]::Reverse($ByEn)
      $i1 = [System.BitConverter]::ToUInt32($BySt, 0)
      $i2 = [System.BitConverter]::ToUInt32($ByEn, 0)
      for ($x = $i1; $x -le $i2; $x++)
      {
         $ip = ([System.Net.IPAddress]$x).GetAddressBytes()
         [Array]::Reverse($ip)
         [System.Net.IPAddress]::Parse($($ip -join '.'))
      }
   }
   $timeout = 2000
   $IPrange = New-IPRange $StartAddress $EndAddress

   $IpTotal = $IPrange.Count

   Get-Event -SourceIdentifier "ID-Ping*" | Remove-Event
   Get-EventSubscriber -SourceIdentifier "ID-Ping*" | Unregister-Event

   $IPrange | ForEach-Object {
      [string]$VarName = "Ping_" + $_.Address
      New-Variable -Name $VarName -Value (New-Object System.Net.NetworkInformation.Ping)
      Register-ObjectEvent -InputObject (Get-Variable $VarName -ValueOnly) -EventName PingCompleted -SourceIdentifier "ID-$VarName"
      (Get-Variable $VarName -ValueOnly).SendAsync($_, $timeout, $VarName)
      Remove-Variable $VarName
      try
      {
         $pending = (Get-Event -SourceIdentifier "ID-Ping*").Count
      }
      catch [System.InvalidOperationException] { Write-Error $Error }
      $index = [array]::indexof($IPrange, $_)
      Write-Progress -Activity "Sending ping to" -Id 1 -Status $_.IPAddressToString -PercentComplete (($index / $IpTotal) * 100)
      Write-Progress -Activity "ICMP requests pending" -Id 2 -ParentId 1 -Status ($index - $pending) -PercentComplete (($index - $pending) / $IpTotal * 100)
      Start-Sleep -Milliseconds $Interval
   }

   Write-Progress -Activity "Done sending ping requests" -Id 1 -Status 'Waiting' -PercentComplete 100

   While ($pending -lt $IpTotal)
   {
      Wait-Event -SourceIdentifier "ID-Ping*" | Out-Null
      Start-Sleep -Milliseconds 10
      $pending = (Get-Event -SourceIdentifier "ID-Ping*").Count
      Write-Progress -Activity "ICMP requests pending" -Id 2 -ParentId 1 -Status ($IpTotal - $pending) -PercentComplete (($IpTotal - $pending) / $IpTotal * 100)
   }

   if ($RawOutput)
   {
      $Reply = Get-Event -SourceIdentifier "ID-Ping*" | ForEach-Object {
         If ($_.SourceEventArgs.Reply.Status -eq "Success")
         {
            $_.SourceEventArgs.Reply
         }
         Unregister-Event $_.SourceIdentifier
         Remove-Event $_.SourceIdentifier
      }

   }
   else
   {
      $Reply = Get-Event -SourceIdentifier "ID-Ping*" | ForEach-Object {
         If ($_.SourceEventArgs.Reply.Status -eq "Success")
         {
            $_.SourceEventArgs.Reply | Select-Object @{
               Name = "IPAddress"   ; Expression = { $_.Address }
            },
            @{Name = "Bytes"       ; Expression = { $_.Buffer.Length } },
            @{Name = "Ttl"         ; Expression = { $_.Options.Ttl } },
            @{Name = "ResponseTime"; Expression = { $_.RoundtripTime } }
         }
         Unregister-Event $_.SourceIdentifier
         Remove-Event $_.SourceIdentifier
      }
   }
   if ($Null -eq $Reply)
   {
      Write-Verbose "Ping-IPrange : No ip address responded" -Verbose
   }

   return $Reply
}# END Function

########################################################################################################################
#
########################################################################################################################

function Rename-ScheduledTask
{
   <#
.SYNOPSIS
Renames a scheduled task on a computer by copying an existing task to a new task, then deleting the original task.
 
.DESCRIPTION
Renames a scheduled task on a computer by copying an existing task to a new task, then deleting the original task. Requires schedule service version 1.2 or later (i.e., at least Windows Vista or Server 2008). If you rename a scheduled task that has saved credentials, you must recreate the task's credentials (if you omit the -TaskCredential parameter, you will be prompted for credentials).
 
.PARAMETER TaskName
The scheduled task you want to rename. If you don't specify a folder name, the root folder ("\") is the default. If the task has saved credentials, you will be prompted for the task's credentials if you don't specify -TaskCredential.
 
.PARAMETER NewName
The new name for the task. You can specify a new folder name for the task. If you don't specify a different folder name, the default is to save the new task in the same folder as the original task. If the task has saved credentials, you will be prompted for the task's credentials if you don't specify -TaskCredential. If the folder name does not exist, it will be created.
 
.PARAMETER ComputerName
The computer on which the task exists. The current computer is the default.
 
.PARAMETER ConnectionCredential
The connection to the task scheduler service will be made using these credentials. If you don't specify this parameter, the currently logged on user's credentials are assumed.
 
.PARAMETER TaskCredential
If the scheduled task you are renaming has saved credentials, use these credentials when creating the new task. If the scheduled task has saved credentials and you don't specify this parameter, you will be prompted for new credentials.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask "Scheduled Task 0" "Scheduled Task 1"
This command renames \Scheduled Task 0 to \Scheduled Task 1.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask \TaskFolder1\Task0 Task1
This command renames \TaskFolder1\Task0 to \TaskFolder1\Task1.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask \TaskFolder0\MyTask1 \TaskFolder1\MyTask1
This command renames \TaskFolder0\MyTask1 to \TaskFolder1\MyTask1.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask MessageTask0 TestMessage -ComputerName server1
This command renames MessageTask0 to TestMessage on server1.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask Test0 Test1 -ComputerName server1 -ConnectionCredential (Get-Credential)
This command renames \Task0 to \Test1 on server1. The connection to server1 is established using prompted credentials.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask FileCopy1 FileCopy2 -ComputerName server2
If the task File1Copy1 on server2 has saved credentials, this command will prompt for credentials when renaming FileCopy1 to FileCopy2.
 
.EXAMPLE
PS C:\> Rename-ScheduledTask FileCopy1 FileCopy2 -ComputerName server2 -TaskCredential $Cred
If the task File1Copy1 on server2 has saved credentials, this command will use the credentials in the $Cred variable when renaming FileCopy1 to FileCopy2.
#>


   param(
      [parameter(Position = 0, Mandatory = $TRUE)] [String] $TaskName,
      [parameter(Position = 1, Mandatory = $TRUE)] [String] $NewName,
      [String] $ComputerName = $ENV:ComputerName,
      [System.Management.Automation.PSCredential] $ConnectionCredential,
      [System.Management.Automation.PSCredential] $TaskCredential
   )

   $MIN_SCHEDULER_VERSION = "1.2"
   $TASK_ILLEGAL_CHARS = @('/', ':', '*', '?', '"', '<', '>', '|')
   $TASK_CREATE = 2
   $TASK_LOGON_PASSWORD = 1

   # SUPPORTING FUNCTIONS ########################################################

   # Returns the specified PSCredential object's password as a plain-text string
   function get-plaintextpwd($credential)
   {
      $credential.GetNetworkCredential().Password
   }

   # Returns a version number as a string (x.y); e.g. 65537 (10001 hex) returns "1.1"
   function convertto-versionstr([Int] $version)
   {
      $major = [Math]::Truncate($version / [Math]::Pow(2, 16)) -band 65535
      $minor = $version -band 65535
      "$($major).$($minor)"
   }

   # Returns a string "x.y" as a version number; e.g., "1.3" returns 65539 (10003 hex)
   function convertto-versionint([String] $version)
   {
      $parts = $version.Split(".")
      $major = [Int] $parts[0] * [Math]::Pow(2, 16)
      $major -bor [Int] $parts[1]
   }

   # Returns whether the specified task name is valid (does not contain illegal characters)
   function test-validtaskname($name)
   {
      foreach ($char in $name.ToCharArray())
      {
         if ($TASK_ILLEGAL_CHARS -contains $char) { return $FALSE }
      }
      $TRUE
   }

   # Returns a list of all tasks starting at the specified task folder
   function get-taskname($taskFolder)
   {
      $tasks = $taskFolder.GetTasks(0)
      $tasks | ForEach-Object { $_.Path }
      $taskFolders = $taskFolder.GetFolders(0)
      $taskFolders | ForEach-Object { get-taskname $_ }
   }

   # MAIN BODY OF SCRIPT #########################################################

   # Throw an error if the new task name is not valid
   if (-not (test-validtaskname $NewName))
   {
      throw "Task name cannot contain any of the following characters: $TASK_ILLEGAL_CHARS"
   }

   # Assume root tasks folder if not specified
   if (-not $TaskName.Contains("\")) { $TaskName = "\$TaskName" }
   # If new name specified without folder name, assume same folder as original name
   if (-not $NewName.Contains("\"))
   {
      $NewName = Join-Path (Split-Path $TaskName -Parent) $NewName
   }

   # Throw an error if the original and new names are the same
   if ($TaskName -eq $NewName)
   {
      throw "-TaskName and -NewName parameters cannot specify the same name."
   }

   # Try to create the TaskService object; throw an error on failure
   try
   {
      $taskService = New-Object -ComObject "Schedule.Service"
   }
   catch [System.Management.Automation.PSArgumentException]
   {
      throw $_
   }

   # Assume $NULL for the schedule service connection parameters unless -ConnectionCredential used
   $userName = $domainName = $connectPwd = $NULL
   if ($ConnectionCredential)
   {
      # Get user name, domain name, and plain-text copy of password from PSCredential object
      $userName = $ConnectionCredential.UserName.Split("\")[1]
      $domainName = $ConnectionCredential.UserName.Split("\")[0]
      $connectPwd = get-plaintextpwd $ConnectionCredential
   }

   # Try to connect to the schedule service on the computer; throw an error on failure
   try
   {
      $taskService.Connect($ComputerName, $userName, $domainName, $connectPwd)
   }
   catch [System.Management.Automation.MethodInvocationException]
   {
      throw $_
   }

   # Scheduler service must meet minimum version criteria
   $minVersion = convertto-versionint $MIN_SCHEDULER_VERSION
   if ($taskService.HighestVersion -lt $minVersion)
   {
      throw ("Schedule service on $ComputerName is version $($taskService.HighestVersion) " +
         "($(convertto-versionstr($taskService.HighestVersion))) - service must be version " +
         "$MIN_SCHEDULER_VERSION ($(convertto-versionint $MIN_SCHEDULER_VERSION)) or higher.")
   }

   # Get a reference to the root schedule folder
   $rootFolder = $taskService.GetFolder("\")

   # Retrieve a list of all registered tasks' names
   $taskNames = get-taskname $rootFolder

   # Throw an error if we can't find the task
   if (-not ($taskNames -contains $TaskName))
   {
      throw "Scheduled task not found on $ComputerName - $TaskName"
   }

   # Throw an error if the new task name already exists
   if ($taskNames -contains $NewName)
   {
      throw "Scheduled task already exists on $ComputerName - $NewName"
   }

   # Get the TaskDefinition object for the original task
   $taskDef = $rootFolder.GetTask($TaskName).Definition

   # The task doesn't need a password unless its LogonType is TASK_LOGON_PASSWORD
   $taskPwd = $NULL
   if ($taskDef.Principal.LogonType -eq $TASK_LOGON_PASSWORD)
   {
      # If -TaskCredential not specified, prompt for credentials; exit script if cancel
      if (-not $TaskCredential)
      {
         $TaskCredential = $HOST.UI.PromptForCredential("Task Credentials", "Please specify credentials for the scheduled task.", "$ENV:USERDOMAIN\$ENV:USERNAME", "")
         if (-not $TaskCredential)
         {
            throw "You must specify credentials."
         }
      }
      # Retrieve plain-text copy of password from PSCredential object
      $taskPwd = get-plaintextpwd $TaskCredential
   }

   # Create new task and delete the original task; throw an error on failure
   try
   {
      # Create a new task as a copy of the original one (void cast prevents output)
      [Void] $rootFolder.RegisterTaskDefinition($NewName, $taskDef, $TASK_CREATE,
         $taskDef.Principal.UserId, $taskPwd, $taskDef.Principal.LogonType)
      # Get the reference to the original task's folder and delete the original task
      $taskFolder = $taskService.GetFolder((Split-Path $TaskName -Parent))
      $taskFolder.DeleteTask((Split-Path $TaskName -Leaf), $NULL)
   }
   catch [System.Management.Automation.MethodInvocationException]
   {
      throw $_
   }
} # END Function

########################################################################################################################
#
########################################################################################################################

Function Reset-WindowsUpdate
{
   [OutputType([String])]
   [CmdletBinding(SupportsShouldProcess)]
   param()
   <#
.SYNOPSIS
Resets the Windows Update components
 
.DESCRIPTION
Resets all of the Windows Updates components to DEFAULT SETTINGS.
 
.OUTPUTS
Results are printed to the console.
 
.NOTES
Written by: Ryan Nemeth
#>

   Write-Output "1. Stopping Windows Update Services..."
   Stop-Service -Name BITS
   Stop-Service -Name wuauserv
   Stop-Service -Name appidsvc
   Stop-Service -Name cryptsvc

   Write-Output "2. Remove QMGR Data file..."
   Remove-Item "$env:allusersprofile\Application Data\Microsoft\Network\Downloader\qmgr*.dat" -ErrorAction SilentlyContinue

   Write-Output "3. Renaming the Software Distribution and CatRoot Folder..."
   Rename-Item $env:systemroot\SoftwareDistribution SoftwareDistribution.bak -ErrorAction SilentlyContinue
   Rename-Item $env:systemroot\System32\Catroot2 catroot2.bak -ErrorAction SilentlyContinue

   Write-Output "4. Removing old Windows Update log..."
   Remove-Item $env:systemroot\WindowsUpdate.log -ErrorAction SilentlyContinue

   Write-Output "5. Resetting the Windows Update Services to defualt settings..."
   "sc.exe sdset bits D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)"
   "sc.exe sdset wuauserv D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)"

   Set-Location $env:systemroot\system32

   Write-Output "6. Registering some DLLs..."
   regsvr32.exe /s atl.dll
   regsvr32.exe /s urlmon.dll
   regsvr32.exe /s mshtml.dll
   regsvr32.exe /s shdocvw.dll
   regsvr32.exe /s browseui.dll
   regsvr32.exe /s jscript.dll
   regsvr32.exe /s vbscript.dll
   regsvr32.exe /s scrrun.dll
   regsvr32.exe /s msxml.dll
   regsvr32.exe /s msxml3.dll
   regsvr32.exe /s msxml6.dll
   regsvr32.exe /s actxprxy.dll
   regsvr32.exe /s softpub.dll
   regsvr32.exe /s wintrust.dll
   regsvr32.exe /s dssenh.dll
   regsvr32.exe /s rsaenh.dll
   regsvr32.exe /s gpkcsp.dll
   regsvr32.exe /s sccbase.dll
   regsvr32.exe /s slbcsp.dll
   regsvr32.exe /s cryptdlg.dll
   regsvr32.exe /s oleaut32.dll
   regsvr32.exe /s ole32.dll
   regsvr32.exe /s shell32.dll
   regsvr32.exe /s initpki.dll
   regsvr32.exe /s wuapi.dll
   regsvr32.exe /s wuaueng.dll
   regsvr32.exe /s wuaueng1.dll
   regsvr32.exe /s wucltui.dll
   regsvr32.exe /s wups.dll
   regsvr32.exe /s wups2.dll
   regsvr32.exe /s wuweb.dll
   regsvr32.exe /s qmgr.dll
   regsvr32.exe /s qmgrprxy.dll
   regsvr32.exe /s wucltux.dll
   regsvr32.exe /s muweb.dll
   regsvr32.exe /s wuwebv.dll

   Write-Output "7) Removing WSUS client settings..."
   REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" /v AccountDomainSid /f
   REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" /v PingID /f
   REG DELETE "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" /v SusClientId /f

   Write-Output "8) Resetting the WinSock..."
   netsh winsock reset
   netsh winhttp reset proxy

   Write-Output "9) Delete all BITS jobs..."
   Get-BitsTransfer | Remove-BitsTransfer

   Write-Output "11) Starting Windows Update Services..."
   Start-Service -Name BITS
   Start-Service -Name wuauserv
   Start-Service -Name appidsvc
   Start-Service -Name cryptsvc

   Write-Output "12) Forcing discovery..."
   wuauclt /resetauthorization /detectnow

   Write-Output "Process complete. Please reboot your computer."
}# END Function

########################################################################################################################
# Find HyperV Hosts in Forests
########################################################################################################################

function Find-HyperVHost
{
   function Find-HyperVHostsInForest
   {
      [OutputType([String])]
      [cmdletbinding()]
      param(
         [string]$forest
      )
      try
      {
         Import-Module ActiveDirectory -ErrorAction Stop
      }
      catch
      {
         Write-Warning "Failed to import Active Directory module. Cannot continue. Aborting..."
         break;
      }

      $domains = (Get-ADForest -Identity $forest).Domains
      foreach ($domain in $domains)
      {
         #"$domain`: `n"
         [string]$dc = (Get-ADDomainController -DomainName $domain -Discover -NextClosestSite).HostName
         try
         {
            $hyperVs = Get-ADObject -Server $dc -Filter 'ObjectClass -eq "serviceConnectionPoint" -and Name -eq "Microsoft Hyper-V"' -ErrorAction Stop;
         }
         catch
         {
            "Failed to query $dc of $domain";
         }
         foreach ($hyperV in $hyperVs)
         {
            $x = $hyperV.DistinguishedName.split(",")
            $HypervDN = $x[1..$x.Count] -join ","

            if ( !($HypervDN -match "CN=LostAndFound"))
            {
               $Comp = Get-ADComputer -Id $HypervDN -Prop *
               $OutputObj = New-Object PSObject -Prop (
                  @{
                     HyperVName = $Comp.Name
                     OSVersion  = $($comp.operatingSystem)
                  })
               $OutputObj
            }
         }
      }
   }
   function Show-Forest
   {
      [OutputType([String])]
      $AllForests = Get-ADForest | Select-Object Name;
      if ($AllForests.length -gt 1)
      {
         #for ($i=0;$i -lt $forests.length;$i++){$forests[$i].Name;}
         $AllForests | ForEach-Object { $_.Name; }
      }
      else
      {
         $AllForests.Name;
      }
   }
   $forests = Show-Forest;
   $forests | ForEach-Object { Find-HyperVHostsInForest $_.Name; }
}