PPS.psm1

# Override Write-Verbose in this module so calling function is added to the message
function script:Write-Verbose
{
    [CmdletBinding()]
    param
    (
       [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] [String] $Message
    )

    begin
    {}

    process
    {
        try
        {
            $PSBoundParameters['Message'] = $((Get-PSCallStack)[1].Command) + ': ' + $PSBoundParameters['Message']
        }
        catch
        {}

        Microsoft.PowerShell.Utility\Write-Verbose @PSBoundParameters
    }

    end
    {}
}

function Connect-Pps
{
    <#
        .SYNOPSIS
            Connect to Pleasant Password Server

        .DESCRIPTION
            Connect to Pleasant Password Server

        .PARAMETER Uri
            URI of server to connect to, eg. https://password.company.tld:10001

        .PARAMETER Credential
            Credential object with username and password used to connect to Pleasant Password Server with

        .PARAMETER Username
            Username used to connect to Pleasant Password Server with

        .PARAMETER Password
            Password used to connect to Pleasant Password Server with

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            Connect-Pps -Uri https://password.company.tld:10001
    #>


    [CmdletBinding(DefaultParameterSetName='Default')]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]
        $Uri,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName='Credential')]
        [pscredential]
        $Credential,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName='UserPass')]
        [string]
        $Username,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName='UserPass')]
        [string]
        $Password,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')

        # FIXXXME - should this be moved out - so it is run when module is imported?
        if (-not $script:SessionList)
        {
            Write-Verbose -Message 'Creating session list'
            $script:SessionList = @{}
        }
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $Uri = $Uri -replace '/$'

            if (-not ($Credential -or $Username -or $Password))
            {
                $PSBoundParameters['Credential'] = Get-Credential -Message 'PPS'
            }


            $oauth2Params = @{
                Uri          = "$Uri/OAuth2/Token"
                ReturnHeader = $true
            }
            $PSBoundParameters.GetEnumerator() | Where-Object -Property Key -In -Value 'Credential','Username','Password' | ForEach-Object -Process {
                $oauth2Params[$_.Key] = $_.Value
            }

            $script:SessionList[$Session] = [PSCustomObject] @{
                Uri     = $Uri
                Headers = Connect-OAuth2 @oauth2Params
            }
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}

function Get-PpsEntry
{
    <#
        .SYNOPSIS
            Get credential entry from Pleasant Password Server

        .DESCRIPTION
            Get credential entry from Pleasant Password Server

        .PARAMETER Id
            ID of entry to get info about

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            Get-PpsEntry -Id 5cbfabe7-70ee-4041-a1e0-263c9170f650
    #>


    [CmdletBinding(DefaultParameterSetName='Default')]
    [OutputType([PSCustomObject], ParameterSetName='Default')]
    [OutputType([PSCredential], ParameterSetName='PSCredential')]
    param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [guid]
        $Id,

        [Parameter(ParameterSetName='PSCredential', Mandatory=$true)]
        [switch]
        $PSCredential,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $p = @{
                Session = $Session
            }

            $entry          = Invoke-PpsApiRequest @p -Uri "credential/$Id"
            $entry.Password = Invoke-PpsApiRequest @p -Uri "credential/$Id/password"

            # Return
            if ($PSCredential)
            {
                [pscredential]::new($entry.Username, ($entry.Password | ConvertTo-SecureString -AsPlainText -Force))
            }
            else
            {
                $entry
            }
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}
function Get-PpsGroup
{
    <#
        .SYNOPSIS
            Get credential group (folder) from Pleasant Password Server

        .DESCRIPTION
            Get credential group (folder) from Pleasant Password Server

        .PARAMETER Id
            ID of group to get info about

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            Get-PpsGroup

        .EXAMPLE
            Get-PpsGroup -Id f6190167-a9a1-4386-a8cd-ae46008c9188
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Position=0)]
        [guid]
        $Id,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $p = @{
                Session = $Session
            }

            $folder          = Invoke-PpsApiRequest @p -Uri "credentialgroup/$Id"

            # Return
            $folder
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}
function Invoke-PpsApiRequest
{
    <#
        .SYNOPSIS
            Invoke API request against Pleasant Password Server

        .DESCRIPTION
            Invoke API request against Pleasant Password Server

        .PARAMETER Uri
            xxx

        .PARAMETER Method
            xxx

        .PARAMETER Data
            xxx

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            xxx
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $Uri,

        [Parameter()]
        [Microsoft.PowerShell.Commands.WebRequestMethod]
        $Method = 'Get',

        [Parameter()]
        [object]
        $Data,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')

        # FIXXXME - should this be moved out - so it is run when module is imported?
        if (-not $script:SessionList)
        {
            Write-Verbose -Message 'Creating session list'
            $script:SessionList = @{}
        }
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            if (-not ($s = $script:SessionList[$Session]))
            {
                throw "Session <$Session> not found"
            }

            $requestParams = @{
                Method          = $Method
                Uri             = "$($s.Uri)/api/v4/rest/$Uri"  # FIXXXME - v4 should'n be hardcoded here!
                Headers         = $s.Headers
                UseBasicParsing = $true
            }
            if ($Data)
            {
                $requestParams['Body']        = ConvertTo-Json -Depth 9 -InputObject $Data
                $requestParams['ContentType'] = 'application/json; charset=utf-8'
            }

            Write-Verbose -Message ($requestParams | ConvertTo-Json)

            # Return
            Invoke-RestMethod @requestParams
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}
function New-PpsEntry
{
    <#
        .SYNOPSIS
            Create new credential entry in Pleasant Password Server

        .DESCRIPTION
            Create new credential entry in Pleasant Password Server

        .PARAMETER Entry
            Object with data to create in Pleasant Password Server

        .PARAMETER GroupId
            ID of credential group to create credential entry in

        .PARAMETER Name
            Name of credential in Pleasant Password Server

        .PARAMETER Username
            Username of credential in Pleasant Password Server

        .PARAMETER Password
            Password of credential in Pleasant Password Server

        .PARAMETER Url
            Url of credential in Pleasant Password Server

        .PARAMETER Notes
            Notes of credential in Pleasant Password Server

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            New-PpsEntry -GroupId f6190167-a9a1-4386-a8cd-ae46008c9188 -Name name -Username uname -Password pw -Url http://abc -Notes "This i a note"

        .EXAMPLE
            @{GroupId='f6190167-a9a1-4386-a8cd-ae46008c9188'; Name='name'; Username='user'; Password='pw'} | New-PpsEntry
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(ParameterSetName='Entry', Mandatory=$true, ValueFromPipeline=$true)]
        [PSCustomObject]
        $Entry,

        [Parameter(ParameterSetName='Properties', Mandatory=$true)]
        [Parameter(ParameterSetName='PSCredential', Mandatory=$true)]
        [guid]
        $GroupId,

        [Parameter(ParameterSetName='Properties')]
        [Parameter(ParameterSetName='PSCredential')]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $Name,

        [Parameter(ParameterSetName='Properties')]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $Username,

        [Parameter(ParameterSetName='Properties')]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $Password,

        [Parameter(ParameterSetName='PSCredential', Mandatory=$true)]
        [PSCredential]
        $PSCredential,

        [Parameter(ParameterSetName='Properties')]
        [Parameter(ParameterSetName='PSCredential')]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $Url,

        [Parameter(ParameterSetName='Properties')]
        [Parameter(ParameterSetName='PSCredential')]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $Notes,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $p = @{
                Session = $Session
            }

            if (-not $Entry)
            {
                if ($PSCredential)
                {
                    $Username = $PSCredential.UserName
                    $Password = $PSCredential.GetNetworkCredential().Password
                }

                $Entry = [PSCustomObject] @{
                    GroupId  = $GroupId
                    Name     = $Name
                    Username = $Username
                    Password = $Password
                    Url      = $Url
                    Notes    = $Notes
                }
            }

            $id = Invoke-PpsApiRequest @p -Uri "credential" -Data $Entry -Method Post

            # Return
            [PSCustomObject] @{
                Id  = $id
                Uri = "$($script:SessionList[$Session].Uri)/WebClient/Main?itemId=$id"
            }
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}
function New-PpsGroup
{
    <#
        .SYNOPSIS
            Create new credential group (folder) from Pleasant Password Server

        .DESCRIPTION
            Create new credential group (folder) from Pleasant Password Server

        .PARAMETER Group
            Object contining group info

        .PARAMETER ParentId
            ID of parent group

        .PARAMETER Name
            Name of the new group (folder)

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            New-PpsGroup -ParentId f6190167-a9a1-4386-a8cd-ae46008c9188 -Name "New Password Group"
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName='Group')]
        [PSCustomObject]
        $Group,

        [Parameter(Mandatory=$true, ParameterSetName='Properties')]
        [guid]
        $ParentId,

        [Parameter(Mandatory=$true, ParameterSetName='Properties')]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $p = @{
                Session = $Session
            }

            if (-not $Group)
            {
                $Group = [PSCustomObject] @{
                    ParentId = $ParentId
                    Name     = $Name
                }
            }

            $id = Invoke-PpsApiRequest @p -Uri "credentialgroup" -Data $Group -Method Post

            # Return
            [PSCustomObject] @{
                Id  = $id
                Uri = "$($script:SessionList[$Session].Uri)/WebClient/Main?itemId=$id"
            }
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}
function Update-PpsEntry
{
    <#
        .SYNOPSIS
            Update existing credential entry in Pleasant Password Server

        .DESCRIPTION
            Update existing credential entry in Pleasant Password Server

        .PARAMETER Entry
            Object with updated info

        .PARAMETER Session
            Makes it possible to connect to multiple Pleasant Password Servers

        .EXAMPLE
            $e=Get-PpsEntry -Id c079a48c-a465-4605-9477-2b4baa743e6f; $e.Username='user'; $e|Update-PpsEntry
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [PSCustomObject]
        $Entry,

        [Parameter()]
        [string]
        $Session = 'Default'
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $p = @{
                Session = $Session
            }

            $id = $Entry.Id
            $null = Invoke-PpsApiRequest @p -Uri "credential/$($Entry.Id)" -Data $Entry -Method Put

            # Return
            [PSCustomObject] @{
                Id  = $id
                Uri = "$($script:SessionList[$Session].Uri)/WebClient/Main?itemId=$id"
            }
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}
Export-ModuleMember -Function New-PpsEntry
Export-ModuleMember -Function New-PpsGroup
Export-ModuleMember -Function Connect-Pps
Export-ModuleMember -Function Update-PpsEntry
Export-ModuleMember -Function Invoke-PpsApiRequest
Export-ModuleMember -Function Get-PpsEntry
Export-ModuleMember -Function Get-PpsGroup