Functions/Get-EncryptedString.ps1

## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx

#region Function Get-EncryptedString
Function Get-EncryptedString
    {
        <#
          .SYNOPSIS
          Decrypts the specified value by using the specifed decryption key, and outputs the information to the pipeline for further usage.
 
          .DESCRIPTION
          Provides a secure method of using sensitive data within scripts as long as best practices are followed. Keeping decryption keys separate from encrypted data is ideal.
           
          .PARAMETER EncryptedData
          Specifies that the previously encrypted value that will be decrypted.
           
          .PARAMETER Path
          Specifies that the value that will be encrypted.
 
          .PARAMETER DecryptionKey
          Specifies a valid Base64 encoded byte array that will be used to decrypt the encrypted data.
 
          .EXAMPLE
          New-EncryptedString -Value 'YourStringValue' | Get-EncryptedString
 
          .EXAMPLE
          $NewEncryptedStringInfo = New-EncryptedString -Value 'YourStringValue' -Verbose
 
          $GetEncryptedStringInfo = Get-EncryptedString -EncryptedData ($NewEncryptedStringInfo.EncryptedData) -DecryptionKey ($NewEncryptedStringInfo.DecryptionKey) -Verbose
 
          Write-Output -InputObject ($GetEncryptedStringInfo) | Format-List -Property *
             
          .EXAMPLE
          #Both The encrypted data and the decryption key value are provided as function parameters.
 
          [String]$GetEncryptedString_EncryptedData = "76492d1116743f0423413b16050a5345MgB8AHoAbQBaAHIATQAyAHYAUAB4AEsAKwBMAEUAQwBiAC8AQgBNAFkAYgBYAHcAPQA9AHwAOQAzADYANQBkADcAMwAwADEAMgA0ADMAMgA5AGIAYwBhAGMAZABhADgANgA5ADUAMAA1AGEAMQBlAGYAZQAyAGQAOQBjADEAZAA0ADcAYwBhAGYAYgA3ADAAYwBhAGYANwBjAGYAZgAyAGQAMABmAGYANAAyAGYAZQBjADQAMwA="
          [String]$GetEncryptedString_DecryptionKey = "mM8h7qOcroCBWYb2yEVD/IFBX2Bswd5sUMW3JFob0BE="
          [Switch]$GetEncryptedString_Verbose = $True
 
          [Hashtable]$GetEncryptedStringParameters = @{}
            $GetEncryptedStringParameters.Add('EncryptedData', ($GetEncryptedString_EncryptedData))
            $GetEncryptedStringParameters.Add('DecryptionKey', ($GetEncryptedString_DecryptionKey))
            $GetEncryptedStringParameters.Add('Verbose', ($GetEncryptedString_Verbose))
 
          $GetEncryptedStringInfo = Get-EncryptedString @GetEncryptedStringParameters
 
          Write-Output -InputObject ($GetEncryptedStringInfo)
 
          .EXAMPLE
          #The encrypted data is stored within the previously exported XML file using the 'New-EncryptedString' function and the decryption key value is provided as a function parameter.
 
          [System.IO.Fileinfo]$GetEncryptedString_Path = "$($Env:Temp.TrimEnd('\'))\New-EncryptedString\New-EncryptionInfo.xml"
          [String]$GetEncryptedString_DecryptionKey = '5pjYQzAhBier/52FPL9X1+KJouYbewVs/3UjeeHudjg='
          [Switch]$GetEncryptedString_Verbose = $True
 
 
          [Hashtable]$GetEncryptedStringParameters = @{}
            $GetEncryptedStringParameters.Add('Path', ($GetEncryptedString_Path))
            $GetEncryptedStringParameters.Add('DecryptionKey', ($GetEncryptedString_DecryptionKey))
            $GetEncryptedStringParameters.Add('Verbose', ($GetEncryptedString_Verbose))
 
          $GetEncryptedStringInfo = Get-EncryptedString @GetEncryptedStringParameters
 
          Write-Output -InputObject ($GetEncryptedStringInfo)
 
          .EXAMPLE
          #The decryption key is stored in a unsecure fashion within the previously exported XML file using the 'New-EncryptedString' function.
 
          [System.IO.Fileinfo]$GetEncryptedString_Path = "$($Env:Temp.TrimEnd('\'))\New-EncryptedString\New-EncryptionInfo.xml"
          [Switch]$GetEncryptedString_Verbose = $True
 
          [Hashtable]$GetEncryptedStringParameters = @{}
            $GetEncryptedStringParameters.Add('Path', ($GetEncryptedString_Path))
            $GetEncryptedStringParameters.Add('Verbose', ($GetEncryptedString_Verbose))
 
          $GetEncryptedStringInfo = Get-EncryptedString @GetEncryptedStringParameters
 
          Write-Output -InputObject ($GetEncryptedStringInfo)
   
          .NOTES
          Do you have processes or scripts that require you to provide a password?
          Against the desires of your security officer, do you have to save those passwords in plain text, in your scripts?
           
          .LINK
          https://www.sqlshack.com/how-to-secure-your-passwords-with-powershell/
        #>

        
        [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'String', SupportsShouldProcess = $True)]
       
        Param
          (                
              [Parameter(Mandatory=$True, ParameterSetName = 'String', ValueFromPipelineByPropertyName = $True)]
              [ValidateNotNullOrEmpty()]
              [String]$EncryptedData,
                         
              [Parameter(Mandatory=$True, ParameterSetName = 'File')]
              [ValidateNotNullOrEmpty()]
              [ValidatePattern('^.*\.xml$')]
              [ValidateScript({Test-Path -Path $_})]
              [System.IO.FileInfo]$Path,
              
              [Parameter(Mandatory=$True, ParameterSetName = 'String', ValueFromPipelineByPropertyName = $True)]
              [Parameter(Mandatory=$False, ParameterSetName = 'File', ValueFromPipelineByPropertyName = $True)] 
              [ValidateNotNullOrEmpty()]
              [ValidatePattern('^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$')]
              [String]$DecryptionKey,
                                                            
              [Parameter(Mandatory=$False)]
              [Switch]$ContinueOnError        
          )
                    
        Begin
          {
              [ScriptBlock]$ErrorHandlingDefinition = {
                                                          If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message -Join "`r`n`r`n")"} Else {$ExceptionMessage = "$($_.Exception.Message)"}
          
                                                          [String]$ErrorMessage = "[Error Message: $($ExceptionMessage)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]"

                                                          If ($ContinueOnError.IsPresent -eq $True)
                                                            {
                                                                Write-Warning -Message ($ErrorMessage)
                                                            }
                                                          ElseIf ($ContinueOnError.IsPresent -eq $False)
                                                            {
                                                                Throw ($ErrorMessage)
                                                            }
                                                      }
              
              Try
                {
                    $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt'  ###Monday, January 01, 2019 @ 10:15:34.000 AM###
                    [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)}
                    $DateTimeFileFormat = 'yyyyMMdd'  ###20190403###
                    [ScriptBlock]$GetDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)}
                    [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)}
                    $TextInfo = (Get-Culture).TextInfo
                    
                    #Determine the date and time we executed the function
                      $FunctionStartTime = (Get-Date)
                    
                    [String]$CmdletName = $MyInvocation.MyCommand.Name 
                    
                    $LogMessage = "Function `'$($CmdletName)`' is beginning. Please Wait..."
                    Write-Verbose -Message $LogMessage
              
                    #Define Default Action Preferences
                      $ErrorActionPreference = 'Stop'
                      
                    $LogMessage = "The following parameters and values were provided to the `'$($CmdletName)`' function." 
                    Write-Verbose -Message $LogMessage

                    $FunctionProperties = Get-Command -Name $CmdletName
                    
                    $FunctionParameters = $FunctionProperties.Parameters.Keys
              
                    ForEach ($Parameter In $FunctionParameters)
                      {
                          If (!([String]::IsNullOrEmpty($Parameter)))
                            {
                                $ParameterProperties = Get-Variable -Name $Parameter -ErrorAction SilentlyContinue
                                $ParameterValueCount = $ParameterProperties.Value | Measure-Object | Select-Object -ExpandProperty Count
                          
                                If ($ParameterValueCount -gt 1)
                                  {
                                      $ParameterValueStringFormat = ($ParameterProperties.Value | ForEach-Object {"`"$($_)`""}) -Join "`r`n"
                                      $LogMessage = "$($ParameterProperties.Name):`r`n`r`n$($ParameterValueStringFormat)"
                                  }
                                Else
                                  {
                                      $ParameterValueStringFormat = ($ParameterProperties.Value | ForEach-Object {"`"$($_)`""}) -Join ', '
                                      $LogMessage = "$($ParameterProperties.Name): $($ParameterValueStringFormat)"
                                  }
                           
                                Switch ($ParameterProperties.Name)
                                  {
                                      {([String]::IsNullOrEmpty($_) -eq $False) -and ($_ -inotmatch "^Password$|^PW$|^Passphrase$")}
                                        {
                                            Write-Verbose -Message $LogMessage
                                        }     
                                  }
                            }
                      }

                    $LogMessage = "Execution of `'$($CmdletName)`' began on $($FunctionStartTime.ToString($DateTimeLogFormat))"
                    Write-Verbose -Message $LogMessage                
                }
              Catch
                {
                    $ErrorHandlingDefinition.Invoke()
                }
          }

        Process
          {           
              Try
                {                                      
                    [Hashtable]$OutputProperties = [Ordered]@{}
                
                    Switch ($PSCmdlet.ParameterSetName)
                      {
                          {($_ -imatch 'String')}
                            {                                            
                                [Byte[]]$DecryptionKey = [System.Convert]::FromBase64String($DecryptionKey)
                        
                                [System.Security.SecureString]$DecryptedDataAsSecureString = ConvertTo-SecureString -String ($EncryptedData) -Key ($DecryptionKey)

                                $DecryptedDataAsBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($DecryptedDataAsSecureString)
                                            
                                [String]$DecryptedData = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($DecryptedDataAsBSTR)
                            }
                  
                          {($_ -imatch 'File')}
                            {
                                $EncryptionInfo = Import-CLIXML -Path ($Path.FullName)
                                
                                Switch ($Null -ine $EncryptionInfo)
                                  {
                                      {($_ -eq $True)}
                                        {
                                            $EncryptionInfoProperties = $EncryptionInfo | Get-Member | Where-Object {($_.MemberType -imatch '^Property$|^NoteProperty$')}
                                            
                                            [Boolean]$IsEncryptedDataIncluded = $EncryptionInfoProperties | Where-Object {($_.Name -imatch '^EncryptedData$')}
                                            
                                            Switch ($False)
                                              {
                                                  {($IsEncryptedDataIncluded)}
                                                    {
                                                        $ErrorMessage = "The XML file `"$($Path.FullName)`" does not contain an `"EncryptedData`" property. No further action will be taken."
                                                        Throw [System.Management.Automation.TerminateException] "$($ErrorMessage)"
                                                    }
                                              }
                                    
                                            [Boolean]$IsDecryptionKeyIncluded = $EncryptionInfoProperties | Where-Object {($_.Name -imatch '^DecryptionKey$')}
                                            
                                            Switch ($IsDecryptionKeyIncluded)
                                              {
                                                  {($_ -eq $True)}
                                                    {
                                                        [Byte[]]$DecryptionKey = [System.Convert]::FromBase64String($EncryptionInfo.DecryptionKey)
                                                    }
                                                    
                                                  {($_ -eq $False)}
                                                    {
                                                        Switch ($PSBoundParameters.ContainsKey('DecryptionKey'))
                                                          {
                                                              {($_ -eq $True)}
                                                                {
                                                                    [Byte[]]$DecryptionKey = [System.Convert]::FromBase64String($DecryptionKey)
                                                                }
                                                                
                                                              {($_ -eq $False)}
                                                                {
                                                                    [String]$ErrorMessage = @"
The decryption key was not detected within `"$($Path.FullName)`".
 
Please specify the decryption key as a function parameter and ensure that the value is a Base64 encoded byte array.
 
Example:
 
`$Null = Add-Type -AssemblyName 'System.Web'
 
[Byte[]]`$EncryptionBytes = [System.Text.Encoding]::Unicode.GetBytes([System.Web.Security.Membership]::GeneratePassword(32, 16))
`$Null = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes(`$EncryptionBytes)
`$EncryptionKey = [System.Convert]::ToBase64String(`$EncryptionBytes)
 
Write-Output -InputObject (`$EncryptionKey)
"@


                                                                    Throw [System.Management.Automation.ParameterBindingException] "$($ErrorMessage)"
                                                                }
                                                          }   
                                                    }
                                              }
                                              
                                            [String]$EncryptedData = ($EncryptionInfo.EncryptedData)
                                                                                                               
                                            [System.Security.SecureString]$DecryptedDataAsSecureString = ConvertTo-SecureString -String ($EncryptedData) -Key ($DecryptionKey)

                                            $DecryptedDataAsBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($DecryptedDataAsSecureString)
                                            
                                            [String]$DecryptedData = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($DecryptedDataAsBSTR)
 
                                            $OutputProperties.Add('Path', ($Path))
                                        }
                                        
                                      {($_ -eq $False)}
                                        {
                                            $ErrorMessage = "The XML file `"$($Path.FullName)`" does not contain valid data. No further action will be taken."
                                            Throw [System.Management.Automation.TerminateException] "$($ErrorMessage)"
                                        }
                                  }
                            }
                      }
                      
                    $OutputProperties.Add('DecryptedDataAsSecureString', ($DecryptedDataAsSecureString))
                    $OutputProperties.Add('DecryptedData', ($DecryptedData))
                    $OutputProperties.Add('ParameterSetName', ($PSCmdlet.ParameterSetName))
                }
              Catch
                {
                    $ErrorHandlingDefinition.Invoke()
                }
          }
        
        End
          {                                        
              Try
                {
                    #Write .NET object returned by this function to the powershell pipeline
                      $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputProperties)    
                      Write-Output -InputObject ($OutputObject)
                
                    #Determine the date and time the function completed execution
                      $FunctionEndTime = (Get-Date)

                      $LogMessage = "Execution of `'$($CmdletName)`' ended on $($FunctionEndTime.ToString($DateTimeLogFormat))"
                      Write-Verbose -Message $LogMessage

                    #Log the total script execution time
                      $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime)

                      $LogMessage = "Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)"
                      Write-Verbose -Message $LogMessage
                    
                    $LogMessage = "Function `'$($CmdletName)`' is completed."
                    Write-Verbose -Message $LogMessage
                }
              Catch
                {
                    $ErrorHandlingDefinition.Invoke()
                }
          }
    }
#endregion