ADLAB PowerShell source file: lib-analytics.ps1

(C) Ondrej Sevecek, 2019 - www.sevecek.com, ondrej@sevecek.com



param(
    [bool] $neverModifyAnything = $false
    )

$global:analytics_NeverModifyAnything = $neverModifyAnything

# ============================================================
# Note: Make sure that the lib-common has been loaded       ||
#                                                           ||

if (($global:adlabVersion -lt 1) -or (-not $global:libCommonScriptInitialized)) {

  $msgLibCommonNotInitialized = 'ADLAB: lib-common not loaded or initialized. Exiting'
  Write-Host $msgLibCommonNotInitialized -ForegroundColor Red
  throw $msgLibCommonNotInitialized
  exit 1
}

$adlabVersionThisLib = 25
$adlabReleaseDateThisLib = [DateTime]::Parse('2016-12-01')
DBG ('Library loaded: {0} | v{1} | {2:yyyy-MM-dd}' -f (Split-Path -leaf $MyInvocation.MyCommand.Definition), $adlabVersionThisLib, $adlabReleaseDateThisLib)

#                                                           ||
# Note: Make sure that the lib-common has been loaded       ||
# ============================================================





function global:Read-Credential ([string] $name, [switch] $domainPassword)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  Define-Win32API

  [int] $credType = 1
  [bool] $success = $true

  if ($domainPassword) {

    $credType = 2
    #$success = Duplicate-ProcessToken lsass -returnStatus $true
  }

  if ($success) {

    [IntPtr] $pCredential = [IntPtr]::Zero;
    [bool] $win32Res = [Sevecek.Win32Api.ADVAPI32]::CredRead($name, $credType, 0, ([ref] $pCredential))
    [int] $lastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
    DBGIF ('Cannot read credential: {0} | {1}' -f $name, $lastError) { -not $win32Res }

    if ($win32Res) {

      $credential = [Sevecek.Win32Api.ADVAPI32+Credential] [System.Runtime.InteropServices.Marshal]::PtrToStructure($pCredential, ([Type] [Sevecek.Win32Api.ADVAPI32+Credential]))
      DBGIF ('Invalid credential obtained: {0} | {1} | {2}' -f $name, $credential.UserName, $credential.TargetName) { (Is-Null $credential) -or (Is-EmptyString $credential.UserName) -or ($credential.TargetName -ne $name) }
      DBG ('Credential obtained: {0} | {1} | {2} | {3:X8}' -f $credential.TargetName, $credential.UserName, $credential.CredentialBlobSize, $credential.Flags)

      if (Is-NonNull $credential) {

        [Sevecek.Win32Api.ADVAPI32]::CredFree($pCredential)
      }
    }
  }
}


function global:Get-LsaSecrets (
  [string] $name,
  [bool] $returnAsByteArray,
  [ValidateScript({ (-not $global:analytics_NeverModifyAnything) -or (-not $_) })] [bool] $forceMachineSecretsDecryptionByKeyCopyOperation = $false
  )

# Note: machine secrets can be read only by "operating system" as documentation says
#       although nobody knows what the "operating system" means, there is a method to decrypt the data
#       when the whole secret key is copied to another one with an arbitrary (non-machine) name
#       If you try to decrypt a machine secrets with the LsaRetrievePrivateData(), you receive 0xC0000022 ACCESS_DENIED error
#       Yet, this is a very sensitive operation, so use the key copy method only for test purposes outside any production environment!
#       Machine secrets are those with names starting with M$, NL$, _SC_, RasDialParams, RasCredentials

{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  
  [System.Collections.ArrayList] $hndlList = @()
  [Collections.ArrayList] $secrets = @()

  $success = Duplicate-ProcessToken lsass -returnStatus $true

if ($success) {

  # Note: this was only a test to decrypt _SC_ secrets without
  #         going to the key copy operations
  <#
  Adjust-Privilege (Lookup-Privilege SeAssignPrimaryTokenPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeAuditPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeCreatePermanentPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeCreateTokenPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeRelabelPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeTrustedCredManAccessPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeEnableDelegationPrivilege) $true $true

  Adjust-Privilege (Lookup-Privilege SeDebugPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeBackupPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeRestorePrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeTcbPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeSecurityPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeCreateGlobalPrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeSystemProfilePrivilege) $true $true
  Adjust-Privilege (Lookup-Privilege SeTakeOwnershipPrivilege) $true $true

  Adjust-Privilege (Lookup-Privilege SeDebugPrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeRestorePrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeBackupPrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeTcbPrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeSecurityPrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeCreateGlobalPrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeSystemProfilePrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeTakeOwnershipPrivilege) $true
  Adjust-Privilege (Lookup-Privilege SeSecurityPrivilege) $true
  #>

  $secretsRegPath = 'HKLM:\SECURITY\POLICY\Secrets'
  $secretsRegPathWin32 = 'HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets'

  DBG ('Read the registry keys first')
  DBGSTART
  $secretKeys = dir $secretsRegPath
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBG ('Found secrets: {0}' -f (Get-CountSafe $secretKeys))
  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $secretKeys) -lt 1 }

  if ((Get-CountSafe $secretKeys) -gt 0) {


    [bool] $lsaAllowReturningUnencryptedSecretsEnabled = $false
    [Collections.ArrayList] $lsaAllowReturningUnencryptedSecretsEnabledBackup = @()
    #[bool] $lsaAllowReturningUnencryptedSecretsOriginallyPresent = $false

try {

    if (($global:thisOSVersionNumber -ge 6.2) -and $forceMachineSecretsDecryptionByKeyCopyOperation) {

      DBG ('We are running on Windows 8/2012 or newer, thus LSA policy protection must be disabled')
      # Note: support mentions are here: http://support.microsoft.com/kb/2743127
      #       Cisco Anyconnect goes here: http://www.cisco.com/c/en/us/td/docs/security/vpn_client/anyconnect/anyconnect31/release/notes/anyconnect31rn.html

<#
      DBG ('Get the LsaAllowReturningUnencryptedSecrets value')
      DBGSTART
      $lsaItemProps = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Lsa)
      $lsaAllowReturningUnencryptedSecretsOriginallyPresent = Is-NonNull ((Get-Member -Input $lsaItemProps -MemberType NoteProperty) | ? { $_.Name -eq 'LsaAllowReturningUnencryptedSecrets' })
      [int] $originalLsaAllowReturningUnencryptedSecrets = $lsaItemProps.LsaAllowReturningUnencryptedSecrets
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Current value of the LsaAllowReturningUnencryptedSecrets: {0}' -f $originalLsaAllowReturningUnencryptedSecrets)
      DBG ('Is the value present at all in the registry: {0}' -f $lsaAllowReturningUnencryptedSecretsOriginallyPresent)

      if ($originalLsaAllowReturningUnencryptedSecrets -ne 1) {

        DBG ('Turn on the LsaAllowReturningUnencryptedSecrets setting')

        DBGSTART
        Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name LsaAllowReturningUnencryptedSecrets -Value 1 -Type DWord -Force
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        $lsaAllowReturningUnencryptedSecretsEnabled = $true
      }
#>
        Set-RegistryValue HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -name LsaAllowReturningUnencryptedSecrets -value 1 -type DWord -withBackup ([ref] $lsaAllowReturningUnencryptedSecretsEnabledBackup)
    }


    function Join-RegistryPathSafe ([string] $parent, [string] $child)
    {
      # Note: some secret registry keys have weird names, such as SCM:{guidguid-guid-guid-guidguidguid}
      #       which fails with error: Cannot find drive.
      #       Thus I have to implement this stupid method

      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $parent }
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $child }
      DBGIF $MyInvocation.MyCommand.Name { $parent -notlike "$secretsRegPathWin32\?*" }

      [string] $result = $null

      if ((Is-ValidString $parent) -and (Is-ValidString $child) -and ($parent -like "$secretsRegPathWin32\?*")) {

        if ($parent -like '*\') {

          $result = '{0}{1}' -f $parent, $child.Trim('\')
        
        } else {

          $result = '{0}\{1}' -f $parent, $child.Trim('\')
        }
      
      } else {

        throw ('Error: invalid Join-RegistryPathSafe parameters: {0} | {1}' -f $parent, $child)
      }

      return $result
    }


    function Get-RegistryLeafPathSafe ([string] $path)
    {
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $path }
      DBGIF $MyInvocation.MyCommand.Name { $path -notlike "$secretsRegPathWin32\?*" }

      [string] $result = $null

      if ((Is-ValidString $path) -and ($path -like "$secretsRegPathWin32\?*")) {

        $result = $path.SubString(($path.LastIndexOf('\') + 1))

      } else {

        throw ('Error: invalid Join-RegistryPathSafe parameters: {0} | {1}' -f $parent, $child)
      }

      return $result
    }


    foreach ($oneSecretKey in $secretKeys) {

      $oneSecretName = Get-RegistryLeafPathSafe $oneSecretKey.Name
      DBG ('Work on one secret: {0}' -f $oneSecretName)

      $oneSecurityDescriptorBinary = Read-RegistryValueRaw -rootKey $null -subKey (Join-RegistryPathSafe $oneSecretKey.Name SecDesc) -value ''
      $oneSecurityDescriptor = New-Object System.Security.AccessControl.RawSecurityDescriptor $oneSecurityDescriptorBinary, 0
      $oneSecurityDescriptorOwner = (New-Object System.Security.Principal.SecurityIdentifier $oneSecurityDescriptor.Owner).Translate([System.Type]::GetType('System.Security.Principal.NTAccount')).Value
      $oneSecurityDescriptorSDDL = $oneSecurityDescriptor.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)
      
      # Note: SDDL example for service accounts _SC_xxx: O:BAG:SYD:(A;;CCDCSDRCWDWO;;;BA)(A;;RC;;;WD)
      DBG ('SD: ownerSID = {0} | owner = {1} | sddl = {2}' -f $oneSecurityDescriptor.Owner, $oneSecurityDescriptorOwner, $oneSecurityDescriptorSDDL)

      $oneCurrentTimeBinary = Read-RegistryValueRaw -rootKey $null -subKey (Join-RegistryPathSafe $oneSecretKey.Name CupdTime) -value ''
      $oneCurrentTime = [DateTime]::FromFileTime([BitConverter]::ToInt64($oneCurrentTimeBinary, 0))
      DBG ('Current time: {0:s}' -f $oneCurrentTime)

      $onePreviousTimeBinary = Read-RegistryValueRaw -rootKey $null -subKey (Join-RegistryPathSafe $oneSecretKey.Name OupdTime) -value ''
      $onePreviousTime = [DateTime]::FromFileTime([BitConverter]::ToInt64($onePreviousTimeBinary, 0))
      DBG ('Previous time: {0:s}' -f $onePreviousTime)


      if ((Is-ValidString $name) -and ($oneSecretName -ne $name)) {

        DBG ('Skipping, not our secret')
        continue
      }

      
      <#
      # Note: we should not modify anything in the registry
      #       we cope here with a really sensitive thing
               
      DBG ('We should correct the security descriptor (its SDDL) first, to be able to read the secret')
      $newSDDL = '{0}(A;;GA;;;BA)' -f $oneSecurityDescriptorSDDL
      DBG ('New SDDL: {0}' -f $newSDDL)
      DBGSTART
      $newSD = New-Object System.Security.AccessControl.RawSecurityDescriptor $newSDDL
      [byte[]] $newSDBinary = New-Object byte[] $newSD.BinaryLength
      $newSD.GetBinaryForm($newSDBinary, 0)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      Write-RegistryValueRaw -rootKey $null -subKey (Join-RegistryPathSafe $oneSecretKey.Name SecDesc) -value '' -data $newSDBinary -type none
      #>

      $lsaPolicyHandle = $null
      $lsaPolicyHandle = Open-LsaPolicyHandle -accessMask ([Sevecek.Win32Api.ADVAPI32]::POLICY_GET_PRIVATE_INFORMATION) #[Sevecek.Win32Api.ADVAPI32]::POLICY_ALL_ACCESS


      [string] $secretDataStr = ''
      [byte[]] $secretDataArray = $null

      if (Is-ValidHandle $lsaPolicyHandle) {

        [void] $hndlList.Add($lsaPolicyHandle)
    
        function Call-LsaRetrievePrivateData ([IntPtr] $lsaPolicyHandle, [string] $oneSecretName, [bool] $doNotAssert22, [ref] $ntStatus) {

          DBG ('Secret name to be retrieved: {0} | {1}' -f $oneSecretName, $oneSecretName.Length)
          $secretName = $null
          $secretName = New-Object Sevecek.Win32Api.LSA_UNICODE_STRING
          DBGSTART
          $secretName.Buffer = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($oneSecretName) 
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          $secretName.Length = [Uint16] ($oneSecretName.Length * ([System.Text.UnicodeEncoding]::CharSize)) 
          $secretName.MaximumLength = [Uint16] (($oneSecretName.Length + 1) * ([System.Text.UnicodeEncoding]::CharSize)) 

          DBG ('Retrieve the secret data')
          DBGSTART
          [IntPtr] $privateData = [IntPtr]::Zero 
          $resNTSTATUS = [Sevecek.Win32Api.ADVAPI32]::LsaRetrievePrivateData($lsaPolicyHandle, [ref] $secretName, [ref] $privateData) 
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('WIN32API Result: 0x{0:X} | {1:D}' -f $resNTSTATUS, $resNTSTATUS)
                                                      
          $doNotAssert = ($resNTSTATUS -eq 0xC0000022L) -and $doNotAssert22
          DBGIF $MyInvocation.MyCommand.Name { ($resNTSTATUS -ne 0) -and (-not $doNotAssert) }
          DBGIF $MyInvocation.MyCommand.Name { (-not (Is-ValidHandle $privateData)) -and (-not $doNotAssert) }

          DBGIF $MyInvocation.MyCommand.Name { ($resNTSTATUS -ne 0) -and (Is-ValidHandle $privateData) }

          $ntStatus.Value = $resNTSTATUS


          DBG ('Free the secret name buffer memory')
          DBGSTART
          [System.Runtime.InteropServices.Marshal]::FreeHGlobal($secretName.Buffer)
          DBGER
          DBGEND


          DBG ('Private data pointer: {0}' -f $privateData)
          return $privateData
        }


        [UInt32] $ntStatus = 0
        [IntPtr] $privateData = [IntPtr]::Zero
        [bool] $oneHasToBeCopied = $false
        $privateData = Call-LsaRetrievePrivateData $lsaPolicyHandle $oneSecretName $forceMachineSecretsDecryptionByKeyCopyOperation ([ref] $ntStatus)


        if (($ntStatus -eq 0xC0000022L) -and $forceMachineSecretsDecryptionByKeyCopyOperation) {

          $oneHasToBeCopied = $true

          DBG ('The value was not decrypted as it is probably a "machine" secret: {0}' -f $oneSecretName)
          # Note: as documented, only some prefixes denote machine secrets M$, NL$, _SC_, RasDialParams, RasCredentials
          DBGIF $MyInvocation.MyCommand.Name { ($oneSecretName -notlike 'M$*') -and ($oneSecretName -notlike 'NL$*') -and ($oneSecretName -notlike '_SC_*') -and ($oneSecretName -notlike 'RasDialParams*') -and ($oneSecretName -notlike 'RasCredentials*') }

          $sevecekTempSecret = 'SevecekTemporaryManualSecret'

          DBG ('Create the temporary secret keys: {0} | {1} | {2}' -f $secretsRegPath, $sevecekTempSecret, ($tempSubKeys -join ','))
          DBGIF $MyInvocation.MyCommand.Name { Test-Path $secretsRegPath\$sevecekTempSecret }

          if (-not (Test-Path $secretsRegPath\$sevecekTempSecret)) {
try {
            $tempSubKeys = @('CurrVal', 'CupdTime', 'OldVal', 'OupdTime', 'SecDesc')

            DBGSTART
            [void] (mkdir $secretsRegPath\$sevecekTempSecret)
            $tempSubKeys | % { [void] (mkdir $secretsRegPath\$sevecekTempSecret\$_) }
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

            $tempSubKeys | % {
          
              $tempSourceBinary = Read-RegistryValueRaw -rootKey $null -subKey (Join-RegistryPathSafe $oneSecretKey.Name $_) -value ''
              Write-RegistryValueRaw -rootKey $null -subKey $secretsRegPath\$sevecekTempSecret\$_ -value '' -data $tempSourceBinary -type none
            }

            $privateData = Call-LsaRetrievePrivateData $lsaPolicyHandle $sevecekTempSecret $false ([ref] $ntStatus)
}

finally {

            DBG ('Remove the temporary secret keys again')
            DBGSTART
            Remove-Item $secretsRegPath\$sevecekTempSecret -Recurse -Force
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
}
          }
        }


        if (($ntStatus -eq 0) -and (Is-ValidHandle $privateData)) {

          DBG ('Decode the secret data')
          DBGSTART
          [Sevecek.Win32Api.LSA_UNICODE_STRING] $secretDataUS = [Sevecek.Win32Api.LSA_UNICODE_STRING] [System.Runtime.InteropServices.Marshal]::PtrToStructure($privateData, ([Type] [Sevecek.Win32Api.LSA_UNICODE_STRING])) 
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          DBG ('Secret data obtained. Copy to a byte array: {0}' -f $secretDataUS.Length)
          $secretDataArray = New-Object byte[] $secretDataUS.Length
          DBGSTART
          [System.Runtime.InteropServices.Marshal]::Copy($secretDataUS.Buffer, $secretDataArray, 0, $secretDataUS.Length)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND


          DBG ('Secret data: {0}' -f ([BitConverter]::ToString($secretDataArray)))


          if (-not $returnAsByteArray) {
          
            #DBGIF ('Secret is not safe for direct string conversion: {0}' -f $oneSecretName) { $secretDataUS.Length -lt 2 }
            #DBGIF ('Secret is not safe for direct string conversion: {0}' -f $oneSecretName) { ($secretDataUS.Length % 2) -ne 0 }
            #DBGIF ('Secret is not safe for direct string conversion: {0}' -f $oneSecretName) { ($secretDataArray[($secretDataArray.Length - 1)] -ne 0) -or ($secretDataArray[($secretDataArray.Length - 2)] -ne 0) }

            # Note: this test is not necessary as the PtrToStringAuto is always called with the maximum number of characters
            #       which limit any possibility of read buffer overflow even if the data are not really a string
            # Note: the following function PtrToStringAuto also always assumes the string is in unicode formating as documented

            #if (($secretDataUS.Length -ge 2) -and (($secretDataUS.Length % 2) -eq 0) -and (($secretDataArray[($secretDataArray.Length - 1)] -eq 0) -and ($secretDataArray[($secretDataArray.Length - 2)] -eq 0))) {
            #}

            DBG ('Transform secret data to string')
            DBGSTART
            $secretDataStr = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($secretDataUS.Buffer, ($secretDataUS.Length / 2)) 
            $secretDataStrAnsi = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($secretDataUS.Buffer, $secretDataUS.Length) 
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
  
            DBG ('Secret data strings: {0} | {1}' -f $secretDataStr, $secretDataStrAnsi)
          }


          DBG ('Cleanup the private data memory')
          DBGSTART
          $resNTSTATUS = [Sevecek.Win32Api.ADVAPI32]::LsaFreeMemory($privateData)
          $lastError = [Sevecek.Win32Api.Kernel32]::GetLastError()
          #DBGIF ('Win32 last error: {0}' -f $lastError) { $lastError -ne 0 }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('WIN32API Result: 0x{0:X} | {1}' -f $resNTSTATUS, $resNTSTATUS)
          DBGIF $MyInvocation.MyCommand.Name { $resNTSTATUS -ne 0 }
        }
      }

      
      #DBG ('Restore the original security descriptor: {0}' -f $oneSecurityDescriptorSDDL)
      #Write-RegistryValueRaw -rootKey $null -subKey (Join-RegistryPathSafe $oneSecretKey.Name SecDesc) -value '' -data $oneSecurityDescriptorBinary -type none

      $newSecret = New-Object PSCustomObject
      Add-Member -Input $newSecret -MemberType NoteProperty -Name Secret -Value $oneSecretName
      Add-Member -Input $newSecret -MemberType NoteProperty -Name Bytes -Value $secretDataArray
      Add-Member -Input $newSecret -MemberType NoteProperty -Name BytesString -Value ([BitConverter]::ToString($secretDataArray))
      Add-Member -Input $newSecret -MemberType NoteProperty -Name String -Value $secretDataStr
      Add-Member -Input $newSecret -MemberType NoteProperty -Name StringAnsi -Value $secretDataStrAnsi
      Add-Member -Input $newSecret -MemberType NoteProperty -Name ModifiedCurrent -Value $oneCurrentTime
      Add-Member -Input $newSecret -MemberType NoteProperty -Name ModifiedPrevious -Value $onePreviousTime
      Add-Member -Input $newSecret -MemberType NoteProperty -Name SDDL -Value $oneSecurityDescriptorSDDL
      Add-Member -Input $newSecret -MemberType NoteProperty -Name Owner -Value $oneSecurityDescriptorOwner
      Add-Member -Input $newSecret -MemberType NoteProperty -Name Copied -Value $oneHasToBeCopied

      $secrets += $newSecret


      DBG ('Cleanup LSA handles')
      foreach ($oneHandle in $hndlList) {

        DBG ('Closing LSA handle: {0}' -f $oneHandle)
        DBGSTART
        $resNTSTATUS = [Sevecek.Win32Api.AdvApi32]::LsaClose($oneHandle)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBG ('WIN32API Result: 0x{0:X} | {1}' -f $resNTSTATUS, $resNTSTATUS)
        DBGIF $MyInvocation.MyCommand.Name { $resNTSTATUS -ne 0 }
      }

      $hndlList.Clear()
    }

}

finally {

  DBG ('Did we set the LsaAllowReturningUnencryptedSecrets previously: {0}' -f $lsaAllowReturningUnencryptedSecretsEnabled)
  if ($lsaAllowReturningUnencryptedSecretsEnabled) {

    DBGIF $MyInvocation.MyCommand.Name { ($global:thisOSVersionNumber -lt 6.2) -or (-not $forceMachineSecretsDecryptionByKeyCopyOperation) }

<#
    if (-not $lsaAllowReturningUnencryptedSecretsOriginallyPresent) {

      DBG ('Turn off the LsaAllowReturningUnencryptedSecrets setting again by deleting the value')
      DBGSTART
      Remove-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name LsaAllowReturningUnencryptedSecrets -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    
    } else {

      DBG ('Turn off the LsaAllowReturningUnencryptedSecrets setting again to its original value: {0}' -f $originalLsaAllowReturningUnencryptedSecrets)
      DBGSTART
      Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name LsaAllowReturningUnencryptedSecrets -Value $originalLsaAllowReturningUnencryptedSecrets -Type DWord -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }
#>
    Restore-RegistryChangeBackup $lsaAllowReturningUnencryptedSecretsEnabledBackup
  }
}


  }

  RevertTo-Self
}


  DBG ('Returning secrets: {0}' -f $secrets.Count)
  DBGIF $MyInvocation.MyCommand.Name { $secrets.Count -lt 1 }

  return ,$secrets
}




# SIG # Begin signature block
# MIIYLgYJKoZIhvcNAQcCoIIYHzCCGBsCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUb+q7KXaQ8nKMijrf2l1KHOdZ
# Em2gghNeMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B
# AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG
# A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh
# d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg
# Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV
# UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu
# dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q
# WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC
# i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4
# ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3
# +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI
# fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd
# BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG
# CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro
# YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV
# HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y
# MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf
# plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y
# 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq
# IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3
# DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh
# dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD
# QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE
# BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT
# eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow
# mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0
# jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu
# ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh
# d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz
# C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB
# o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO
# BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw
# Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90
# cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx
# oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy
# bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV
# HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa
# 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH
# bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73
# BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR
# EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW
# yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu
# e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw
# ggTlMIIDzaADAgECAhA5vUKe0oFuutW8yQO0umXnMA0GCSqGSIb3DQEBCwUAMHUx
# CzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSkwJwYDVQQLEyBT
# dGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEjMCEGA1UEAxMaU3RhcnRD
# b20gQ2xhc3MgMiBPYmplY3QgQ0EwHhcNMTYxMjAxMTU1MTEzWhcNMTgxMjAxMTU1
# MTEzWjBRMQswCQYDVQQGEwJDWjEaMBgGA1UECAwRSmlob21vcmF2c2t5IEtyYWox
# DTALBgNVBAcMBEJybm8xFzAVBgNVBAMMDk9uZHJlaiBTZXZlY2VrMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr9E9hNj06bash9JX97kpsqK9Z/ciOBC6
# trI4nvlW9CPwhKBTb5wArhxLYZBG9jWPWrdy1nL/cm5qMqBb/mogYwMwvEYWMvsI
# OOVn6HD9lVhNAovD6PHz0ziBBKIszXTjyUPQaoIlIELovz967m78HJdUZJGxqhlu
# AsS9o9/fEzA7XXUhUuqRKsetuZV/Asfh5sOveeoRsbeW4daTWvtz3TJuULL0w43L
# NVYJkd6LL8cegvLPVZUe1N7skvidEvntdlowQsJlqFdrH3SGKIPKA6ObcY8SZWkE
# QSbVBF8Kum1UT+jN0gm+84FwOg5WqKx+VvTK2ljVWnPrCD0Zzu2oIQIDAQABo4IB
# kzCCAY8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAkGA1Ud
# EwQCMAAwHQYDVR0OBBYEFG2vSo3NhQWILeUs0oN9XzHTejcfMB8GA1UdIwQYMBaA
# FD5ik5rXxxnuPo9JEIVVFSDjlIQcMG0GCCsGAQUFBwEBBGEwXzAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3Auc3RhcnRzc2wuY29tMDcGCCsGAQUFBzAChitodHRwOi8v
# YWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zY2EuY29kZTIuY3J0MDYGA1UdHwQvMC0w
# K6ApoCeGJWh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL3NjYS1jb2RlMi5jcmwwIwYD
# VR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNzbC5jb20vMFEGA1UdIARKMEgwCAYG
# Z4EMAQQBMDwGCysGAQQBgbU3AQIFMC0wKwYIKwYBBQUHAgEWH2h0dHBzOi8vd3d3
# LnN0YXJ0c3NsLmNvbS9wb2xpY3kwDQYJKoZIhvcNAQELBQADggEBAJuRiEvHtIYS
# psmMkPhTz4QOOShN3p5KWdf8vm71A33CR9fds10d8D2B2aE+vjmHJ69GY0bbfg5o
# ZY2Lsq2euL7Da5/hS8+6T3MEtD4hnjfHV7mxmoSfFuy/KDipoV6uwhI+ksqchXYd
# UH+5uCQO0MOO8ITjAgzUQsnZ4UIBHBGeP+e+3ljxSYSXWdPIrgxdR971P/HhWSVf
# KNlmBgEKMQM5Jy0aAd4jxSl/AzdYt0+6pliFJ1peGhdFni2Fm8fu5oN68aTIrNtc
# 5WY7Lzgf+sRTVeWORWS37+1zAD0mjzd8gyfBLxRuaRSfjYxny0rLXelAwfiA3ze2
# DU2Bfg9/rfcwggXYMIIDwKADAgECAhBsO9J+3TyUnpWOKKmzx1egMA0GCSqGSIb3
# DQEBCwUAMH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSsw
# KQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYD
# VQQDEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTEyMTYw
# MTAwMDVaFw0zMDEyMTYwMTAwMDVaMHUxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1T
# dGFydENvbSBMdGQuMSkwJwYDVQQLEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1
# dGhvcml0eTEjMCEGA1UEAxMaU3RhcnRDb20gQ2xhc3MgMiBPYmplY3QgQ0EwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5FARY97LFhiwIMmCtCCbAgXe5
# aBnZFSsdGGnk2hqWBZcuZHkaqT1RM1rQd2r0ApNBw466cBur2Ht0b5jo17mpPmh2
# pImgIqwX1in4u7hhn9IH0GYOMEcgK3ACHv5zCRxxNLXifqmsqKfxjjpABnaSyvd4
# bO9YBXN9f4NQ6aJVAuMArpanxsJke+P4WECVLk17v92CAN5JVaczI+baT/lgo5NV
# cTEkloCViSbIfU6ILeyhOSQZvpomMYk8eJqI0nimOTJJfmXangNDsrX8np+3lXD0
# +6rCZisXRWIaeffyTMHZ31Qj1D50WYdRtX5yev4WgaXoKJQN3lkgXUcytvyHAgMB
# AAGjggFaMIIBVjAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMw
# EgYDVR0TAQH/BAgwBgEB/wIBADAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3Js
# LnN0YXJ0c3NsLmNvbS9zZnNjYS5jcmwwZgYIKwYBBQUHAQEEWjBYMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5zdGFydHNzbC5jb20wMAYIKwYBBQUHMAKGJGh0dHA6
# Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL2NhLmNydDAdBgNVHQ4EFgQUPmKTmtfH
# Ge4+j0kQhVUVIOOUhBwwHwYDVR0jBBgwFoAUTgvvGqRAW6UXaYcwyjRoQ9BBrvIw
# PwYDVR0gBDgwNjA0BgRVHSAAMCwwKgYIKwYBBQUHAgEWHmh0dHA6Ly93d3cuc3Rh
# cnRzc2wuY29tL3BvbGljeTANBgkqhkiG9w0BAQsFAAOCAgEAY6U81bNtJyjY67pT
# rzAL6kpdEtX5mspw+kxjjNdNVH5G6lLnhaEkIxqdpvY/Wdw+UdNtExs+N8efKPSw
# h2m/BxXj2fSeLMwXcwHFookScEER8ez0quCNzioqNHac7LCXPEnQzbtG2FHlePKN
# DWh8eU6KxiAzNzIrIxPthinHGgLTBOACHQM2YTlD8YoU5oN3dLmBOqtH0BDMZoLc
# jEIoEW1zC+TnVb3yU1G0xub6gnN7lP50vbAiHJYrnywQiXaloBV8B9YYfe6Zgvjq
# xwufwFcMVyE3UmCuDTsOpjqDEKpJ25s+FUdkie5VqCS1aaudLo31X+9UvP45pfgy
# RqzyfUnVEhH4ZXxlBWZMzj2Xov5+m/+H3kxYuFA5xdqdshj/Zx00S7PkCSF+8M1N
# CcvFgQwjIw61bZAjDBl3P3a8xNTXsb2CjFdiNKbT3LD6IGeIf0b/EbPf0FXdvBrx
# m0ofMOhnngdPolPYCtoOGtZPAVe/xeu+/ZyKv6TSHlshaUO0iYfsmbXnZ51vvt/k
# kjwms9/qPFxSuE0fjEfF7aQazwREDf2hiVPR0pAhvShtM3oU4XreEFEUWEYHs25f
# YV4WMmxkUKSgmSmwRq45tvtGH4LTb5+cd+iLqK8rBQL0E6xaUjjGfsYx7bueIvqT
# vCkrQvoxMbn/qDHCiypowDVq6TAxggQ6MIIENgIBATCBiTB1MQswCQYDVQQGEwJJ
# TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEpMCcGA1UECxMgU3RhcnRDb20gQ2Vy
# dGlmaWNhdGlvbiBBdXRob3JpdHkxIzAhBgNVBAMTGlN0YXJ0Q29tIENsYXNzIDIg
# T2JqZWN0IENBAhA5vUKe0oFuutW8yQO0umXnMAkGBSsOAwIaBQCgeDAYBgorBgEE
# AYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG
# CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBSu4bXs
# XeYssoAmFu71RNlT7lMYyDANBgkqhkiG9w0BAQEFAASCAQCPcr0LNIhhHSUY8fz6
# EqgTaBU5At7okohmhLHpzbZazhCIkR52E/YJcC+bIUhTkwdGdV5JUpQJ4fQQzlkW
# yVfodRmelOAlzs/rpXP9hEl0q10N21miuOslWjEYmqnNMazKC1Kntk9l33OQtV1Y
# 15Yp2BRkhjcO/Im4SibrW9yL9vBOrFv/W06mlIx5iiWNzr0Umz7eC+aproYXuLHD
# N8LOKdCeYbXbyKiOX1MIVC12fb9LTL6LGtErgtcigRbOKyKU95d2djUbMH79FZM5
# GsVnV9PhBt+oLdaRdIrzUxVfhCgloYxhK/NKO9vPASqN0rbeCy5Tx9vHfqwclste
# jrlRoYICCzCCAgcGCSqGSIb3DQEJBjGCAfgwggH0AgEBMHIwXjELMAkGA1UEBhMC
# VVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTAwLgYDVQQDEydTeW1h
# bnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIENBIC0gRzICEA7P9DjI/r81bgTY
# apgbGlAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ
# KoZIhvcNAQkFMQ8XDTE3MDEyNDIwMjAwN1owIwYJKoZIhvcNAQkEMRYEFDUT0yx+
# lejsjdZIdoSd1VCsODnNMA0GCSqGSIb3DQEBAQUABIIBAJxZjg6lySegqhet0jAR
# L3GTbsxa/3y3OlCLfwrBqjxuAD0yVMMHHBF58wwUu491QZmW6TFPxD5Ycgy9XYQS
# +PQq4+qUfguz4oAVGdpW3MEsG6SVX1e0oQU+6C+gdciGa0NbLG+rqTodMvCQ5ITN
# ZbcSEE3pku7CxbU+ZP9AVAuUkJCQyexy40oflkq7XXLAd8U89926fJ2DzOB+6Pw8
# zF00NUL1dTv2HyMBUPTYysTuGCDtR35hKUvml0F5ulHKIW4IGm/LRMaHEC3byBYe
# 5feIZOyfwOFcTeowbPIP6Dk5q49X/CK549wEJmPLvdGJcvLjVgf7xQDEZrUh22IF
# ot4=
# SIG # End signature block