Read-Credential.ps1

#----------------------------------------------------[Function synopsis]-----------------------------------------------------

Function Read-Credential {

<#
   .SYNOPSIS
    Reads credentials from disk, saved with Write-Credential. Part of CredentialsManager module.
 
   .DESCRIPTION
    Reads credentials (User Name and Password) from disk, saved with Write-Credential.
    Credentials are saved as encrypted data. Encryption is done with current user identifier, so only current user can decrypt saved credentials.
    Other users cannot decrypt content, even if they get access to credential files (i.e. saved on network share).
 
    .EXAMPLE
    $Cred = Read-Credential -Environment Dev
    Gets username and password from files saved in repository and saves it in variable.
 
    .EXAMPLE
    Get-WMIObject Win32_Service -Computer '58.138.249.212' -Credential (Read-Credential -Environment Dev)
    Gets credentials from repository and directly uses it in network operation.
 
    .EXAMPLE
    Get-Item '\\dfs\restricted\*' -Credential (Read-Credential -Environment BAcc)
    Gets B account credentials from repository and directly uses it to access restricted folder content.
 
    .PARAMETER Environment
    Name of environment for which credentials should be obtained. It must be already saved with Write-Credential.
    It is also used as part of file names in Repository.
 
    .PARAMETER Path
    Optional parameter which defines where credentials are saved on the disk.
    If not specified, %APPDATA%\CredentialsManager is used.
    If specified, default value will be updated and re-used on next calls within the same PowerShell session.
 
    .PARAMETER ListAvailable
    If specified, function will list all credentials at default or provided path. Returned object(s) will have added property Environment which is named according to Write-Credential function.
 
    .OUTPUTS
    Function returns Credentials object the same as Get-Credential, System.Management.Automation.PSCredential.
 
    .LINK
    https://www.powershellgallery.com/packages/CredentialsManager
 
    .NOTES
    NAME: Read-Credential
    AUTHOR: Igor Iric, IricIgor@Gmail.com
    CREATEDATE: October, 2015
 
 #>



#-------------------------------------------------[Parameters definitions]--------------------------------------------------

[cmdletbinding()]

Param(
  [parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='Environments',Position=1)][string[]]$Environment,
  [parameter(Mandatory=$false,ValueFromPipeline=$false)][string]$Path=$Script:CredentialsPath,
  [parameter(Mandatory=$true,ValueFromPipeline=$false,ParameterSetName='Available')][switch]$ListAvailable
) #end param


#-------------------------------------------------[Function initialization]--------------------------------------------------
BEGIN {
    # function begin phase

    # process Path parameter, add default value, check if it exists
    if (!$Path) {
        # Path not provided, neither global is defined
        $Script:CredentialsPath = Join-Path -Path $env:APPDATA -ChildPath 'CredentialsManager'
        Write-Verbose -Message "Setting default path to: $Script:CredentialsPath"
        $Path = $Script:CredentialsPath
        } 

    if (!(Test-Path -Path $Path)) {
        Throw "Folder $Path is not existing. Please provide another path, or run Write-Credential first."
    } elseif ($Path -ne $Script:CredentialsPath) {
        # Path correct and specified directly, it will be saved for next usage
        Write-Verbose -Message "Updating default path to $Path."
        $Script:CredentialsPath = $Path
    } 

    # process ListAvailable
    if ($ListAvailable) {
        Write-Verbose -Message 'Obtaining list of available environments'
        <#
        $UserNames = (Get-ChildItem -Path $Path -Filter '*_UserName.cred') -replace '_UserName.cred',''
        $PassNames = (Get-ChildItem -Path $Path -Filter '*_Password.cred') -replace '_Password.cred',''
        if ($UserNames -and $PassNames) {
            $Environment = (Compare-Object -ReferenceObject $UserNames -DifferenceObject $PassNames -ExcludeDifferent -IncludeEqual).InputObject
        }
        #>

        $Environment = (Get-ChildItem -Path $Path -Filter '*.creds') -replace '.creds',''

        if (!$Environment) {
            Write-Warning -Message 'No stored credentials found. Try using Write-Credential, or Read-Credential with Path and ListAvailable parameters.'
        } else {
            Write-Verbose -Message ('Obtained '+($Environment.Count)+' environments.')
        } 
    }
}

#---------------------------------------------------[Function processing]----------------------------------------------------
PROCESS {

    foreach ($E in @($Environment)) {
        # function process phase, executed once for each element in main Prameter
        Write-Verbose -Message '----------------------'
        Write-Verbose -Message "Processing environment $E..."
        $VerboseMessage = "Environment $E processed with issues." # it will be updated at the end, if successfull
        $Cred = $null

        # main code
        #$FileNameUser = Join-Path -Path $Path -ChildPath ($E + '_UserName.cred')
        #$FileNamePass = Join-Path -Path $Path -ChildPath ($E + '_Password.cred')
        $FileName = Join-Path -Path $Path -ChildPath ($E + '.creds')

        #if ((!(Test-Path -Path $FileNameUser)) -or (!(Test-Path -Path $FileNamePass))) {
        if (!(Test-Path -Path $FileName)) {
            Write-Error -Message "Credentials file not found."
        } else {
            Write-Verbose -Message 'Credentials file found.'

            $Content = Get-Content -Path $FileName
            if (($Content.Count) -ne 2) {
                Write-Error -Message "Credentials file not in proper format."
            } else {
            
                try {           
                    Write-Verbose -Message 'Attempting to decrypt data.'
                    #$UserSec = Get-Content -Path $FileNameUser | ConvertTo-SecureString
                    #$PassSec = Get-Content -Path $FileNamePass | ConvertTo-SecureString
                    $UserSec = $Content[0] | ConvertTo-SecureString
                    $PassSec = $Content[1] | ConvertTo-SecureString
                    $UserPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($UserSec))

                    $Cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserPlain,$PassSec
                    Write-Verbose -Message 'Data encryption completed.'
                    $VerboseMessage = "Environment $E processed successfully."
                } catch {
                    if (($Error[0].Exception.Message) -eq 'Key not valid for use in specified state.') {
                        Write-Error 'Data encryption failed. Only user account that saved data can decrypt it.'
                    } else {
                        Write-Error -Message ('Data encryption failed. '+$Error[0].Exception.Message)
                    }
                }
            }
        }

        # return values
        if ($Cred) {
            if ($ListAvailable) {
                Write-Verbose -Message 'Adding Environment property.'
                $Cred | Add-Member -MemberType NoteProperty -Name Environment -Value $E
            }
            Write-Verbose -Message "Returning $E credentials value to output."
            $Cred
        }

        Write-Verbose -Message $VerboseMessage

    } # end of foreach

} # end of function

#-----------------------------------------------------[Function closing]-----------------------------------------------------

END {
    # function closing phase
    Write-Verbose -Message 'Read-Credential finishing.'

}

} # end of function code
#----------------------------------------------------[End of function]------------------------------------------------------

#---------------------------------------------------[Comments section]------------------------------------------------------