PwSh.Fw.Security.psm1

$Script:PROFILEDIR = $env:USERPROFILE
if ($IsLinux) { $Script:PROFILEDIR = $env:HOME }
if ($IsMacOS) { $Script:PROFILEDIR = $env:HOME }
if ($IsWindows) { $Script:PROFILEDIR = $env:USERPROFILE }

<#
.SYNOPSIS
Save credential into a encrypted file
 
.DESCRIPTION
Sometime you need to save credential to be used by scripts running unattended (in cron jobs for example).
This function lets you save an encrypted version in standard way to be easily re-used.
 
.PARAMETER Domain
Domain of the user. Optional
 
.PARAMETER Username
Username of the user.
 
.PARAMETER Password
Password of the user.
 
.PARAMETER Credential
Full credential of the user
 
.EXAMPLE
Save-Credential -Username myuser
 
.EXAMPLE
$cred = Get-Credential
Save-Credential -Credential $cred
 
.EXAMPLE
$cred = Get-Credential
$cred | Save-Credential
 
.NOTES
General notes
#>

function Save-Credential {
    [CmdletBinding(DefaultParameterSetName = 'USERNAME')]
    [OutputType([Boolean])]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="'Save' is a more intuitive verb for this function because it just... well... save your credential into a file.")]
    Param (
        [Parameter(Mandatory = $false, ParameterSetName = 'USERNAME')][string]$Domain,
        [Parameter(Mandatory = $true, ParameterSetName = 'USERNAME')][string]$Username,
        [Parameter(Mandatory = $true, ParameterSetName = 'USERNAME')][SecureString]$Password,
        [Parameter(Mandatory = $false)][string]$TargetHost,
        [Parameter(Mandatory = $true, ParameterSetName = 'CREDENTIAL')][System.Management.Automation.PSCredential]$Credential,
        [Parameter(Mandatory = $false)][string]$Path = $Script:PROFILEDIR
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            'USERNAME' {
                # if ($Username -match "(?<domain>.*)\\(?<username>.*)") {
                # $DOMAIN = $Matches.domain
                # $Username = $Matches.username
                # }
                if ($USERNAME.Contains("\")) {
                    $Domain, $Username = $USERNAME.Split("\")
                }
                if ($Domain) {
                    $Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist "$Domain\$Username",$Password
                } else {
                    $Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist "$Username",$Password
                }
            }
            'CREDENTIAL' {

            }
        }

        $Filename = "$Path/"
        if ($Domain) { $Filename += "$Domain_" }
        if ($TargetHost) { $Filename += "$TargetHost_" }
        $Filename += "$UserName.pwd"

        $Credential.Password | ConvertFrom-SecureString | Out-File $Filename -encoding UTF8 -Force:$force
        if (Test-Path -PathType Leaf $Filename) {
            return $true
        }
        return $false
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Load credential from a encrypted file
 
.DESCRIPTION
This function can load credential previously saved by Save-Credential
 
.PARAMETER Domain
Domain of the user. Optional
 
.PARAMETER Username
Username of the user.
 
.PARAMETER Filename
Path to the credential file if not in standard path.
 
.EXAMPLE
Load-Credential -Username myuser
 
.EXAMPLE
Load-Credential -Filename c:\path\to\user.pwd
 
.NOTES
General notes
#>

function Load-Credential {
    [CmdletBinding(DefaultParameterSetName = 'USERNAME')]
    [OutputType([System.Management.Automation.PSCredential])]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="'Load' is a more intuitive verb for this function because it just... well... load your credential from a file.")]
    Param (
        [Parameter(Mandatory = $false, ParameterSetName = 'USERNAME')][string]$Domain,
        [Parameter(Mandatory = $true, ParameterSetName = 'USERNAME')][string]$Username,
        [Parameter(Mandatory = $false, ParameterSetName = 'USERNAME')][string]$TargetHost,
        [Parameter(Mandatory = $true, ParameterSetName = 'FILENAME')][string]$Filename,
        [Parameter(Mandatory = $false)][string]$Path = $Script:PROFILEDIR
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            'USERNAME' {
                if ($USERNAME.Contains("\")) {
                    $Domain, $Username = $USERNAME.Split("\")
                }
                $Filename = "$Path/"
                if ($Domain) { $Filename += "$Domain_" }
                if ($TargetHost) { $Filename += "$TargetHost_" }
                $Filename += "$UserName.pwd"
            }
            'FILENAME' {
                $Item = Get-Item $Filename -ErrorAction stop
                if ($Item.Basename -match "_.*_") {
                    $Domain, $TargetHost, $Username = $Item.Basename -split '_'
                } elseif ($Item.Basename -match "_") {
                    $Domain, $Username = $Item.Basename -split '_'
                } else {
                    $Username = $Item.Basename
                }
            }
        }
        if (!($Username)) {
            return $null
        }
        if (!(Test-Path -path $Filename -PathType leaf)) {
            return $null
        }
        $Password = get-content $Filename | ConvertTo-SecureString
        if ($Domain) {
            $Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist "$Domain\$Username",$Password
        } else {
            $Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist "$Username",$Password
        }
        return $Credential
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Display credential in plain text
 
.DESCRIPTION
Sometime you need to see credential in plain text to be sure it the good one.
This function just display it
 
.PARAMETER Domain
Domain of the user. Optional
 
.PARAMETER Username
Username of a user.
 
.PARAMETER Credential
Full credential of a user
 
.PARAMETER Filename
Path to the credential file if not in standard path.
 
.EXAMPLE
Show-Credential -Username myuser
 
.EXAMPLE
$cred = Get-Credential
Show-Credential -Credential $cred
 
.NOTES
General notes
#>

function Show-Credential {
    [CmdletBinding(DefaultParameterSetName = 'USERNAME')]
    [OutputType([string])]
    Param (
        [Parameter(Mandatory = $false, ParameterSetName = 'USERNAME')][string]$Domain,
        [Parameter(Mandatory = $true, ParameterSetName = 'USERNAME')][string]$Username,
        [Parameter(Mandatory = $false, ParameterSetName = 'USERNAME')][string]$TargetHost,
        [Parameter(Mandatory = $true, ParameterSetName = 'FILENAME')][string]$Filename,
        [Parameter(Mandatory = $true, ParameterSetName = 'CREDENTIAL')][PSCredential]$Credential,
        [Parameter(Mandatory = $false)][string]$Path = $Script:PROFILEDIR
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            'USERNAME' {
                # $Filename = "$Path/"
                # if ($Domain) { $Filename = "$Domain_" }
                # if ($TargetHost) { $Filename = "$TargetHost_" }
                # $Filename += "$UserName.pwd"
                $Credential = Load-Credential -Domain $Domain -TargetHost $TargetHost -Username $Username -Path $Path
            }
            'FILENAME' {
                $Credential = Load-Credential -Filename $Filename
                # $Item = Get-Item $Filename -ErrorAction stop
                # if ($Item.Basename -match "_.*_") {
                # $Domain, $TargetHost, $Username = $Item.Basename -split '_'
                # } elseif ($Item.Basename -match "_") {
                # $Domain, $Username = $Item.Basename -split '_'
                # } else {
                # $Username = $Item.Basename
                # }
            }
            'CREDENTIAL' {
            }
        }
        $Username = $Credential.UserName
        $Password = $Credential.Password

        $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($Password)
        $result = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
        [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)

        $output = "" | Select-Object TargetHost, Domain, Username, Password
        $output.TargetHost = $TargetHost
        $output.Domain = $Domain
        $output.Username = $Username
        $output.Password = $result
        return $output
    }

    End {
        Write-LeaveFunction
    }
}