# encoding: ascii
# api: powershell
# title: 
# description: A multithreaded remote registry gathering function. Includes the ability to return both a specific subkey value or an entire key of subkey values in a custom psobject.
# version: 1.0.0
# type: function
# author: Zachary Loeber
# license: CC0
# function: Get-RemoteRegistryInformation
# x-poshcode-id: 4368
# x-archived: 2013-08-13T09:21:07
#
#
function Get-RemoteRegistryInformation
{
<#
.SYNOPSIS
   Retrieves registry subkey information.
.DESCRIPTION
   Retrieves registry subkey information. If an explicit subkey is passed then that subkey value
   is returned. Otherwise, all subkeys and their values are returned as a custom psobject.
.PARAMETER ComputerName
   Specifies the target computer for data query.
.PARAMETER Hive
   Registry hive to retrieve from. By default this is 2147483650 (HKLM). Valid hives include:
      HKEY_CLASSES_ROOT = 2147483648
      HKEY_CURRENT_USER = 2147483649
      HKEY_LOCAL_MACHINE = 2147483650
      HKEY_USERS = 2147483651
      HKEY_CURRENT_CONFIG = 2147483653
      HKEY_DYN_DATA = 2147483654
.PARAMETER Key
   Registry key to inspect (ie. SYSTEM\CurrentControlSet\Services\W32Time\Parameters)
.PARAMETER SubKey
   Registry subkey to return data from. If this is not passed then all subkeys within the key being
   inspected are returned as a custom psobject.
.PARAMETER ThrottleLimit
   Specifies the maximum number of systems to inventory simultaneously 
.PARAMETER Timeout
   Specifies the maximum time in second command can run in background before terminating this thread.
.PARAMETER ShowProgress
   Show progress bar information
.EXAMPLE
   PS > Get-RemoteRegistryInformation -Key "SYSTEM\CurrentControlSet\Services\W32Time\Parameters" -Subkey 'Type'

   NT5DS
   
   Description
   -----------
   Return the value of the 'Type' subkey within SYSTEM\CurrentControlSet\Services\W32Time\Parameters of
   HKLM.
.EXAMPLE
   PS > $b = Get-RemoteRegistryInformation -Key "SYSTEM\CurrentControlSet\Services\W32Time\Parameters"
   PS > $b | Select SubKey,SubKeyValue
   
    SubKey                                                         SubKeyValue                                                  
    ------                                                         -----------                                                  
    ServiceDll                                                     C:\Windows\system32\w32time.dll
    ServiceMain                                                    SvchostEntry_W32Time
    ServiceDllUnloadOnStop                                                             
    Type                                                           NT5DS               
    NtpServer                                                             

   Description
   -----------
   Return subkeys and their values within SYSTEM\CurrentControlSet\Services\W32Time\Parameters of
   HKLM.

.NOTES
   Author: Zachary Loeber
   Site: http://www.the-little-things.net/
   Requires: Powershell 2.0

   Version History
   1.0.0 - 08/06/2013
    - Initial release
#>
    [CmdletBinding()]
    Param
    (
        [Parameter(HelpMessage="Computer or computers to gather information from",
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [Alias('DNSHostName','PSComputerName')]
        [string[]]
        $ComputerName=$env:computername,
        
        [Parameter( HelpMessage="Registry Hive (Default is HKLM)." )]
    	[UInt32]
        $Hive = 2147483650,
        
    	[Parameter( Mandatory=$true,
                    HelpMessage="Registry Key to inspect." )]
    	[String]
        $Key,
        
    	[Parameter( HelpMessage="Registry Subkey to retrieve. Do not pass this and all subkeys within the key will be returned instead." )]
    	[String]
        $SubKey = '',
       
        [Parameter(HelpMessage="Maximum number of concurrent threads.")]
        [ValidateRange(1,65535)]
        [int32]
        $ThrottleLimit = 32,
 
        [Parameter(HelpMessage="Timeout before a thread stops trying to gather the information.")]
        [ValidateRange(1,65535)]
        [int32]
        $Timeout = 120,
 
        [Parameter(HelpMessage="Display progress of function.")]
        [switch]
        $ShowProgress,
        
        [Parameter(HelpMessage="Set this if you want the function to prompt for alternate credentials.")]
        [switch]
        $PromptForCredential,
        
        [Parameter(HelpMessage="Set this if you want to provide your own alternate credentials.")]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    Begin
    {
        # Gather possible local host names and IPs to prevent credential utilization in some cases
        Write-Verbose -Message 'Creating local hostname list'
        $IPAddresses = [net.dns]::GetHostAddresses($env:COMPUTERNAME) | Select-Object -ExpandProperty IpAddressToString
        $HostNames = $IPAddresses | ForEach-Object {
            try {
                [net.dns]::GetHostByAddress($_)
            } catch {
                # We do not care about errors here...
            }
        } | Select-Object -ExpandProperty HostName -Unique
        $LocalHost = @('', '.', 'localhost', $env:COMPUTERNAME, '::1', '127.0.0.1') + $IPAddresses + $HostNames
 
        Write-Verbose -Message 'Creating initial variables'
        $runspacetimers       = [HashTable]::Synchronized(@{})
        $runspaces            = New-Object -TypeName System.Collections.ArrayList
        $bgRunspaceCounter    = 0
        
        if ($PromptForCredential)
        {
            $Credential = Get-Credential
        }
        
        Write-Verbose -Message 'Creating Initial Session State'
        $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
        foreach ($ExternalVariable in ('runspacetimers', 'Credential', 'LocalHost'))
        {
            Write-Verbose -Message "Adding variable $ExternalVariable to initial session state"
            $iss.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $ExternalVariable, (Get-Variable -Name $ExternalVariable -ValueOnly), ''))
        }
        
        Write-Verbose -Message 'Creating runspace pool'
        $rp = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $ThrottleLimit, $iss, $Host)
        $rp.Open()
 
        # This is the actual code called for each computer
        Write-Verbose -Message 'Defining background runspaces scriptblock'
        $ScriptBlock = {
            [CmdletBinding()]
            Param
            (
                [Parameter(Position=0)]
                [string]
                $ComputerName,

                [Parameter()]
            	[UInt32]
                $Hive = 2147483650,
                
            	[Parameter()]
            	[String]
                $Key,
                
            	[Parameter()]
            	[String]
                $SubKey = '',

                [Parameter()]
                [int]
                $bgRunspaceID
            )
            $runspacetimers.$bgRunspaceID = Get-Date
            
            try
            {
                Write-Verbose -Message ('Runspace {0}: Start' -f $ComputerName)
                $WMIHast = @{
                    ComputerName = $ComputerName
                    ErrorAction = 'Stop'
                }
                if (($LocalHost -notcontains $ComputerName) -and ($Credential -ne $null))
                {
                    $WMIHast.Credential = $Credential
                }

                # General variables
                $PSDateTime = Get-Date
                
                #region Registry
                Write-Verbose -Message ('Runspace {0}: Registry information' -f $ComputerName)

                # WMI data
                $wmi_data = Get-WmiObject @WMIHast -Class StdRegProv -Namespace 'root\default' -List:$true
                
                # If we have a subkey then we are returning an individual value
                if ($SubKey -ne '')
                {
                    $subkeys = $wmi_data.GetStringValue($Hive, $Key, $Subkey)
                    $ResultObject = $subkeys.sValue
                }
                # Otherwise we are returning a bunch of subkeys and their values
                else
                {
                   $ResultObject = @() #($wmi_data.EnumValues($Hive,$Key)).sNames
                   Foreach ($_subkey in (($wmi_data.EnumValues($Hive,$Key)).sNames))
                   {
                        $ResultProperty = @{
                            'PSComputerName' = $ComputerName
                            'PSDateTime' = $PSDateTime
                            'ComputerName' = $ComputerName
                            'SubKey' = $_subkey
                            'SubKeyValue' = ($wmi_data.GetStringValue($Hive, $Key, $_subkey)).sValue
                        }
                        $ResultObject += New-Object PSObject -Property $ResultProperty
                    }
                }

                Write-Output -InputObject $ResultObject
            }
            catch
            {
                Write-Warning -Message ('{0}: {1}' -f $ComputerName, $_.Exception.Message)
            }
            Write-Verbose -Message ('Runspace {0}: End' -f $ComputerName)
        }
 
        function Get-Result
        {
            [CmdletBinding()]
            Param 
            (
                [switch]$Wait
            )
            do
            {
                $More = $false
                foreach ($runspace in $runspaces)
                {
                    $StartTime = $runspacetimers.($runspace.ID)
                    if ($runspace.Handle.isCompleted)
                    {
                        Write-Verbose -Message ('Thread done for {0}' -f $runspace.IObject)
                        $runspace.PowerShell.EndInvoke($runspace.Handle)
                        $runspace.PowerShell.Dispose()
                        $runspace.PowerShell = $null
                        $runspace.Handle = $null
                    }
                    elseif ($runspace.Handle -ne $null)
                    {
                        $More = $true
                    }
                    if ($Timeout -and $StartTime)
                    {
                        if ((New-TimeSpan -Start $StartTime).TotalSeconds -ge $Timeout -and $runspace.PowerShell)
                        {
                            Write-Warning -Message ('Timeout {0}' -f $runspace.IObject)
                            $runspace.PowerShell.Dispose()
                            $runspace.PowerShell = $null
                            $runspace.Handle = $null
                        }
                    }
                }
                if ($More -and $PSBoundParameters['Wait'])
                {
                    Start-Sleep -Milliseconds 100
                }
                foreach ($threat in $runspaces.Clone())
                {
                    if ( -not $threat.handle)
                    {
                        Write-Verbose -Message ('Removing {0} from runspaces' -f $threat.IObject)
                        $runspaces.Remove($threat)
                    }
                }
                if ($ShowProgress)
                {
                    $ProgressSplatting = @{
                        Activity = 'Getting asset info'
                        Status = '{0} of {1} total threads done' -f ($bgRunspaceCounter - $runspaces.Count), $bgRunspaceCounter
                        PercentComplete = ($bgRunspaceCounter - $runspaces.Count) / $bgRunspaceCounter * 100
                    }
                    Write-Progress @ProgressSplatting
                }
            }
            while ($More -and $PSBoundParameters['Wait'])
        }
    }
    Process
    {
        foreach ($Computer in $ComputerName)
        {
            $bgRunspaceCounter++
            #$psCMD = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock).AddParameter('bgRunspaceID',$bgRunspaceCounter).AddParameter('ComputerName',$Computer).AddParameter('IncludeMemoryInfo',$IncludeMemoryInfo).AddParameter('IncludeDiskInfo',$IncludeDiskInfo).AddParameter('IncludeNetworkInfo',$IncludeNetworkInfo).AddParameter('Verbose',$Verbose)
            $psCMD = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock)
            $null = $psCMD.AddParameter('bgRunspaceID',$bgRunspaceCounter)
            $null = $psCMD.AddParameter('ComputerName',$Computer)
            $null = $psCMD.AddParameter('Hive',$Hive)
            $null = $psCMD.AddParameter('Key',$Key)
            $null = $psCMD.AddParameter('SubKey',$SubKey)
            $null = $psCMD.AddParameter('Verbose',$VerbosePreference)
            $psCMD.RunspacePool = $rp
 
            Write-Verbose -Message ('Starting {0}' -f $Computer)
            [void]$runspaces.Add(@{
                Handle = $psCMD.BeginInvoke()
                PowerShell = $psCMD
                IObject = $Computer
                ID = $bgRunspaceCounter
           })
           Get-Result
        }
    }
 
    End
    {
        Get-Result -Wait
        if ($ShowProgress)
        {
            Write-Progress -Activity 'Getting share session information' -Status 'Done' -Completed
        }
        Write-Verbose -Message "Closing runspace pool"
        $rp.Close()
        $rp.Dispose()
    }
}
