# encoding: ascii
# api: powershell
# title: Compare Reg Keys
# description: Two functions Get-AllRegkey and Compare-AllRegKey which will recursivly retrieve a key’s properties and subkeys, and their properties and subkeys, as an array of objects, across multiple computers. The Compare-AllRegKey function uses Compare-Object to compare the arrays returned from each computer to an array returned from a specified reference computer.
# version: 0.1
# type: script
# author: whertzing56
# license: CC0
# function: Get-AllRegKey
# x-poshcode-id: 5868
# x-archived: 2015-05-29T10:02:26
# x-published: 2015-05-24T14:48:00
#
#
<#
.SYNOPSIS 
Compares Registry Key Properties and subkeys across multiple computers
.DESCRIPTION
The function Get-AllRegKey  will recurse down from a given key, returning an array having
    the key's properties, subkeys, and their properties and subkeys.
Provide Get-AllRegKey a list of computernames, and it will remote to those computers
    and return the properties, etc. of the same key on the remote computer
Provide function Compare-AllRegKey with the name of the reference computer, a list of
    two or more computer names, and it will call Get-AllRegKey to retrieve the key 
    information from all the listed computers, then use Compare-Object to return 
    just the differences.
If you want more control over the Compare-Object step, you should modify the 
    function (suggestions welcome for an efficient/concise way to add Compare-Object 
    parameters to the Compare-AllRegKey function)
  
.PARAMETER ComputerNames
In the Get-AllRegKey function this is a single computer name or an array of computer names
In the Compare-AllRegKey function, this must be an array of at least two computer names
.PARAMETER RegKey
a single registry key/hive from which recursion starts, using the Registry Provider syntax
The value defaults to the current-scoped variable $DefaultRegistryKey
.PARAMETER -ReferenceObject
Applies only to the Compare-AllRegKey function, this parameter identifies the computer
against which the other computers are compared. This string must be one of the computer
names found in the ComputerNames parameter.
.EXAMPLE
C:\PS> Get-AllRegKey
.EXAMPLE
C:\PS> Get-AllRegKey -RegistryKey 'HKLM:\SOFTWARE\Microsoft\PowerShell'
.EXAMPLE
C:\PS> Get-AllRegKey -ComputerNames @('localhost','RemoteCN')
.EXAMPLE
C:\PS> Get-AllRegKey -ComputerNames @('localhost','Computer1','Computer2')
.EXAMPLE
C:\PS> Get-AllRegKey localhost 'HKLM:\SOFTWARE\Microsoft\PowerShell'
.EXAMPLE
C:\PS> Compare-AllRegKey -ComputerNames @('localhost','RemoteCN') 
.EXAMPLE
C:\PS> Compare-AllRegKey -ComputerNames @('CN1','CN2') -ReferenceObject CN2
#>

################################################################################
# Default values for the Registry Key and the list of computernames
$DefaultRegistryKey='HKLM:\SOFTWARE\Microsoft\PowerShell'
$computerNames= @('localhost','ncat099')

################################################################################
# The scriptblock that does the actual work of creating an object representing a 
#  registry key and all it's properties and subkeys and their properties. No Defaults
#  This is recursive and remoteable
$_getRegKeySB = {Param($RegistryHive)
  # Create a local named function
  function _getRegKey {
    Param($RegistryHive)
    # $data is an array, local to each loop of the recursion
    #    initialize it with the name of the hive/key
    $data=@($RegistryHive)
    # Get the hive/keys properties, excluding the ones added by PS
    $props = Get-ItemProperty -Path $RegistryHive | 
      Select * -Exclude PS*Path,PSChildName,PSDrive,PSProvider
    # if $props is empty, piping it to get-member produces an error message
    # so test it for non-null first
    if ($props) {
      $props = $props | get-member -memberType NoteProperty
      # prepend each property with the full name of the key, and add it to $data
      foreach ($p in $props) {$data+=("$RegistryHive`:"+$p.Definition)}
    }
    # recursivly call the same algorithm for any subkeys of the hive/key
    foreach ($sk in (get-item $RegistryHive).GetSubKeyNames()) {
      # if there are any subkeys, append their data to the current data.
      # Use the full name of the key
      $data += (&_getRegKey (($RegistryHive)+'\'+ $sk))
    }
    # the local named function's output is the array representation of the hive/key
    $data
  }
  # Call the local named function
  &_getRegKey $RegistryHive
}

################################################################################
# Across all computers, get the key and subkeys from the registry
#  returns a hash of array objects, keyed by computer name
function Get-AllRegKey {
  Param (
    # Single computer name or an array of computer names. 
    #  Defaults to the "current scoped variable by the same name"
    $computerNames = $computerNames
    # A valid key
    ,$RegistryKey = $DefaultRegistryKey
  )
  # create the empty hash
  $AllRegKey = @{}
  # iterate over each computer name
  foreach ($cn in $computerNames) {
    switch ($cn) {
      # If the computer name is localhost, or the same name as hostname
      #   use the Call operator to call the scriptblock, and assign the array returned 
      #   to the hash using the current computername as the key
      {$_ -match "localhost|" + (hostname)} {
        $AllRegKey.$cn = &$_getRegKeySB $RegistryKey
        break
      }
      # for all other computer names, execute the command remotely using invoke-command
      default {
        # pass the scriptblock to the remote computers
        #  assign the array returned to the hash using the current computername as the key
        $AllRegKey.$cn = (invoke-command -Scriptblock $_getRegKeySB `
            -ArgumentList $RegistryKey -computername "$cn")
      }
    }
  }
  #return the hash of arrays
  $AllRegKey
}

################################################################################
# Across all computers, get the key and subkeys from the registry
#  returns a hash of array objects, keyed by computer name
function Compare-AllRegKey {
  Param (
    # Must be an array, with 2 or more members; 
    #   defaults to the "current scoped variable by the same name"
    $computerNames = $computerNames
    # A valid key
    ,$RegistryKey = $DefaultRegistryKey
    # The name of the computer to use as the reference 
    ,$ReferenceObject
  )
Begin {
  # If the argument for $ReferenceObject is null, then default 
  #    to the first element of $computerNames
  if (!$ReferenceObject) {$ReferenceObject =$computerNames[0]}
  else {
    # Validate that the $referenceObject is an element of $computerNames
    if (!($computerNames -contains $ReferenceObject)) {
          throw ("{0} is not a member of the list {1}" -f $ReferenceObject, `
               ($computerNames -join ','))}
  }
  # Get the Registry Key data for all computers 
  $AllRegKey = Get-AllRegKey $computerNames $RegistryKey
} 
Process {
  # Iterate over the computernames, excluding the $ReferenceObject
  $diff = @{};
  foreach ($cn in $computerNames | Where {$cn -ne $ReferenceObject}) {
    # compare $ReferenceObject to the remaining objects, accumulate into $diff
    $diff.$cn = Compare-Object -ReferenceObject $AllRegKey.$ReferenceObject `
        $AllRegKey.$cn | Where-Object { `
            ($_.SideIndicator -eq '=>') -or ($_.SideIndicator -eq '<=') }
  }
    # Return the difference hash
  $diff
}
}

# The following lines will compare the default registry key 
#  across the default array of computernames
Compare-AllRegKey
