VaultControl.psm1

function Add-ObjectDetail {
    <#
    .SYNOPSIS
        Decorate an object with
            - A TypeName
            - New properties
            - Default parameters
    .DESCRIPTION
        Helper function to decorate an object with
            - A TypeName
            - New properties
            - Default parameters
    .PARAMETER InputObject
        Object to decorate. Accepts pipeline input.
    .PARAMETER TypeName
        Typename to insert.
 
        This will show up when you use Get-Member against the resulting object.
 
    .PARAMETER PropertyToAdd
        Add these noteproperties.
 
        Format is a hashtable with Key (Property Name) = Value (Property Value).
        Example to add a One and Date property:
            -PropertyToAdd @{
                One = 1
                Date = (Get-Date)
            }
    .PARAMETER DefaultProperties
        Change the default properties that show up
    .PARAMETER Passthru
        Whether to pass the resulting object on. Defaults to true
    .EXAMPLE
        #
        # Create an object to work with
        $Object = [PSCustomObject]@{
            First = 'Cookie'
            Last = 'Monster'
            Account = 'CMonster'
        }
        #Add a type name and a random property
        Add-ObjectDetail -InputObject $Object -TypeName 'ApplicationX.Account' -PropertyToAdd @{ AnotherProperty = 5 }
            # First Last Account AnotherProperty
            # ----- ---- ------- ---------------
            # Cookie Monster CMonster 5
        #Verify that get-member shows us the right type
        $Object | Get-Member
            # TypeName: ApplicationX.Account ...
    .EXAMPLE
        #
        # Create an object to work with
        $Object = [PSCustomObject]@{
            First = 'Cookie'
            Last = 'Monster'
            Account = 'CMonster'
        }
        #Add a random property, set a default property set so we only see two props by default
        Add-ObjectDetail -InputObject $Object -PropertyToAdd @{ AnotherProperty = 5 } -DefaultProperties Account, AnotherProperty
            # Account AnotherProperty
            # ------- ---------------
            # CMonster 5
        #Verify that the other properties are around
        $Object | Select -Property *
            # First Last Account AnotherProperty
            # ----- ---- ------- ---------------
            # Cookie Monster CMonster 5
    .NOTES
        This breaks the 'do one thing' rule from certain perspectives...
        The goal is to decorate an object all in one shot
 
        This abstraction simplifies decorating an object, with a slight trade-off in performance. For example:
        10,000 objects, add a property and typename:
            Add-ObjectDetail: ~4.6 seconds
            Add-Member + PSObject.TypeNames.Insert: ~3 seconds
        Initial code borrowed from Shay Levy:
        http://blogs.microsoft.co.il/scriptfanatic/2012/04/13/custom-objects-default-display-in-powershell-30/
 
    .LINK
        http://ramblingcookiemonster.github.io/Decorating-Objects/
    .FUNCTIONALITY
        PowerShell Language
    #>

    [CmdletBinding()]
    param(
        [Parameter( Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$InputObject,

        [Parameter( Mandatory = $false,
            Position = 1)]
        [string]$TypeName,

        [Parameter( Mandatory = $false,
            Position = 2)]
        [System.Collections.Hashtable]$PropertyToAdd,

        [Parameter( Mandatory = $false,
            Position = 3)]
        [ValidateNotNullOrEmpty()]
        [Alias('dp')]
        [System.String[]]$DefaultProperties,

        [boolean]$Passthru = $True
    )

    Begin {
        if($PSBoundParameters.ContainsKey('DefaultProperties')) {
            # define a subset of properties
            $ddps = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet, $DefaultProperties
            $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]$ddps
        }
    }
    Process {
        foreach($Object in $InputObject) {
            switch ($PSBoundParameters.Keys) {
                'PropertyToAdd' {
                    foreach($Key in $PropertyToAdd.Keys) {
                        #Add some noteproperties. Slightly faster than Add-Member.
                        $Object.PSObject.Properties.Add( ( New-Object System.Management.Automation.PSNoteProperty($Key, $PropertyToAdd[$Key]) ) )
                    }
                }
                'TypeName' {
                    #Add specified type
                    [void]$Object.PSObject.TypeNames.Insert(0, $TypeName)
                }
                'DefaultProperties' {
                    # Attach default display property set
                    Add-Member -InputObject $Object -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers
                }
            }
            if($Passthru) {
                $Object
            }
        }
    }
}

Function ConvertTo-InsecureString {
    <#
    .SYNOPSIS
    Returns string value from SecureString input
 
    .DESCRIPTION
    Gets the decoded string value of an encoded SecureString
 
    .PARAMETER SecureString
    The SecureString to decode
 
    .EXAMPLE
    ConvertTo-InsecureString $SecureStringValue
    #>

    [CmdLetBinding()]
    [OutputType('System.String')]
    Param (

        [Parameter(
            Mandatory = $True)]
        [System.Security.SecureString]$SecureString
    )

    Try {

        $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($SecureString)
        [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)

    }

    Finally {

        [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr)

    }

}

Function Invoke-PARClient {

    <#
    .SYNOPSIS
    Defines specified PARClient command and arguments
 
    .DESCRIPTION
    Defines a PARClient process object with arguments required for specific command.
 
    .PARAMETER PARClient
    The Path to PARClient.exe.
    Defaults to value of $Script:PAR.ClientPath, which is set during module import or via Set-PARConfiguration.
 
    .PARAMETER Server
    The name or address of the Vault server to target
 
    .PARAMETER Password
    SecureString of password used for PARClient operations
 
    .PARAMETER Credential
    A credential object containing the password used for PARClient operations
 
    .PARAMETER PassFile
    The path to a "password" file created by PARClient.exe, containing the encrypted password value used for remote
    operations via PARClient
 
    .PARAMETER CommandParameters
    The PARClient command to execute
 
    .PARAMETER PAROptions
    Additional command parameters. By default specifies /Q /C.
    A new /StateFileName file in the local temp directory is specified for each command, and deleted after the command completes,
    so log queries are not limited to records unread by a previous command.
 
    .PARAMETER RemainingArgs
    A catch all parameter, accepts any remaining values from pipeline.
    Intended to suppress errors when piping in an object.
 
    .EXAMPLE
    Invoke-PARClient -Server EPV1 -Password $SecureString -CommandParameters "GetParm Vault DebugLevel"
 
    Invokes the GetParm action against the Vault on EPV1 and returns the DebugLevel parameter value.
 
    .NOTES
        AUTHOR: Pete Maan
 
    #>


    [CmdLetBinding(SupportsShouldProcess)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "RemainingArgs", Justification = "Catch-all for unbound pipeline properties")]
    param(

        [Parameter(
            Mandatory = $False,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$PARClient = $Script:PAR.ClientPath,

        [Parameter(
            Mandatory = $False,
            ValueFromPipelineByPropertyName = $True
        )]
        [int]$Port = $Script:PAR.Port,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "Credential"
        )]
        [pscredential]$Credential,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_})]
        [string]$PassFile,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$CommandParameters,

        [Parameter(Mandatory = $False,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$PAROptions = "/Q /C",

        [Parameter(Mandatory = $False,
            ValueFromPipelineByPropertyName = $False,
            ValueFromRemainingArguments = $true
        )]
        $RemainingArgs
    )

    Begin {

        Try {

            $null = Get-Variable -Name PAR -ErrorAction Stop

            if($PAR.PSObject.Properties.Name -notcontains "ClientPath") {

                Write-Error "Heads Up!" -ErrorAction Stop

            }

        } Catch {throw "PARClient.exe not found `nRun Set-PARConfiguration to set path to PARClient"}

        #Create process
        $Process = New-Object System.Diagnostics.Process

    }

    Process {

        Switch($PSCmdlet.ParameterSetName) {

            "Credential" {

                $ClearTextPassword = $($Credential.GetNetworkCredential().Password)
                $PARCommand = "$Server/$ClearTextPassword"; break

            }

            "Password" {

                $ClearTextPassword = ConvertTo-InsecureString -SecureString $Password
                $PARCommand = "$Server/$ClearTextPassword"; break

            }

            "PassFile" {$PARCommand = "$Server /UsePassFile `"$PassFile`""; break}

        }

        If($Port -gt 0) {

            $PARCommand = "$PARCommand /Port $Port"

        }

        $CommandParameters = $CommandParameters -replace ('^', '"')
        $CommandParameters = $CommandParameters -replace ('$', '"')

        if ($PSCmdlet.ShouldProcess($Server, "$CommandParameters")) {

            $StateFile = Join-Path $env:temp "$([guid]::NewGuid()).tmp"
            $Arguments = "$PARCommand /StateFileName `"$StateFile`" $PAROptions $CommandParameters"

            Write-Debug "Command Arguments: $Arguments"

            #Assign process parameters

            $Process.StartInfo.WorkingDirectory = "$(Split-Path $PARClient -Parent)"
            $Process.StartInfo.Filename = $PARClient
            $Process.StartInfo.Arguments = $Arguments
            $Process.StartInfo.RedirectStandardOutput = $True
            $Process.StartInfo.RedirectStandardError = $True
            $Process.StartInfo.UseShellExecute = $False
            $Process.StartInfo.CreateNoWindow = $True
            $Process.StartInfo.WindowStyle = "hidden"

            #Start Process
            Try {

                $Result = Start-PARClientProcess -Process $Process

            } Finally {

                Remove-Item -Path $StateFile -Force -ErrorAction SilentlyContinue

            }

            $Result | Add-Member -MemberType NoteProperty -Name Server -Value $Server

            if($Result.StdOut -match '((?:^[A-Z]{3,5}[0-9]{3}[A-Z])|(?:ERROR \(\d+\)))(?::)? ([^\r\n]+)') {

                #PARCL002S Authentication failure.
                Write-Error -Message $Matches[2] -ErrorId $Matches[1]

            } ElseIf($Result.StdOut -match '(.+) \((Reason: \d+)\)') {

                #Cannot get parameter DisableExceptionHandling. (Reason: 7)
                Write-Error -Message $Matches[1] -ErrorId $Matches[2]

            } Else {$Result}

        }

    }

    End {

        $Process.Dispose()

    }

}

Function Start-PARClientProcess {

    <#
    .SYNOPSIS
    Starts PARClient process
 
    .DESCRIPTION
    Designed to receive PARClient process object from Invoke-PARClient.
 
    Returns Object containing ExitCode, StdOut & StdErr
 
    .PARAMETER Process
    System.Diagnostics.Process object containing PARClient parameters
 
    .EXAMPLE
    Start-PARClientProcess -Process $Process
 
    Invokes the Start method on the $Process object
 
    .NOTES
        AUTHOR: Pete Maan
 
    #>


    [CmdLetBinding(SupportsShouldProcess)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "ShouldProcess handling is in Invoke-PARClient")]
    param(

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True
        )]
        [System.Diagnostics.Process]$Process
    )

    Begin {

    }

    Process {

        #Start Process
        $Process.start() | Out-Null

        #Read Output Stream First
        $StdOut = $Process.StandardOutput.ReadToEnd()
        $StdErr = $Process.StandardError.ReadToEnd()

        #If you wait for the process to exit before reading StandardOutput
        #the process can block trying to write to it, so the process never ends.
        $Process.WaitForExit()

        [PSCustomObject] @{

            "ExitCode" = $Process.ExitCode
            "StdOut"   = $StdOut
            "StdErr"   = $StdErr

        }

    }

    End {

        $Process.Dispose()

    }

}

# .ExternalHelp VaultControl-help.xml
Function Get-PARComponent {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR", "ENE", "CVM")]
        [string]$Component
    )

    Process {

        $PSBoundParameters.Add("CommandParameters", "Status $Component")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            switch -regex ($Result.StdOut) {

                'running' {$Status = "Running"; break}
                'starting' {$Status = "Starting"; break}
                'stopped' {$Status = "Stopped"; break}
                default {$Status = $Result.StdOut; break}

            }

            [PSCustomObject]@{

                "Server"    = $Result.Server
                "Component" = $Component
                "Status"    = $Status

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Get-PARComponentConfig {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR")]
        [string]$Component,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("DefaultTimeout", "MTU", "SecurityNotification", "DebugLevel", "DisableExceptionHandling",
            "EnableCheck", "EnableReplicate", "EnableFailover", "EnableDBSync", "FailoverMode")]
        [string]$Parameter

    )

    Begin {
        $VaultSet = "DefaultTimeout", "MTU", "SecurityNotification", "DebugLevel", "DisableExceptionHandling"
        $PADRSet = "EnableCheck", "EnableReplicate", "EnableFailover", "EnableDBSync", "FailoverMode"
    }
    Process {

        Switch($Component) {
            "Vault" {
                If($VaultSet -notcontains $Parameter) {throw "Not a valid DBPARM.ini Parameter"}
            }
            "PADR" {
                If($PADRSet -notcontains $Parameter) {throw "Not a valid PADR.ini Parameter"}
            }
        }

        $PSBoundParameters.Add("CommandParameters", "GetParm $Component $($PSBoundParameters["Parameter"])")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            If($Result.StdOut -match "=") {

                $Value = ($Result.StdOut).Split("=")[1]

            } Else {$Value = $Result.StdOut}

            [PSCustomObject]@{

                "Server"    = $Result.Server
                "Component" = $Component
                "Parameter" = $($PSBoundParameters["Parameter"])
                "Value"     = $Value.Trim()

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Get-PARComponentLog {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR", "ENE", "CVM")]
        [string]$Component,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Console", "Trace")]
        [string]$LogFile = "Console",

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [datetime]$TimeFrom,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [int]$Lines
    )

    Begin {

        $PADR = '^\[(\d+\/\d+\/\d+\s+\d+\:\d+\:\d+\.\d+)\]\W+([A-Z]+\d+[A-Z](?:\s))?(.+)$'
        $Vault = '^(\d+\/\d+\/\d+ \d+:\d+:\d+) ([A-Z]+[0-9]+[A-Z]) (.+)$'
        $ENEConsole = '^\[(\d+\/\d+\/\d+\s\W\s\d+\:\d+\:\d+)\]\W+([A-Z]+\d+[A-Z])\s(.+)$'
        $ENETrace = '^\[(\d+\/\d+\/\d+(?:\s\W)\s\d+\:\d+\:\d+)\.\d+\].+\|\s([A-Z]+(?:[A-Z]|\d)[A-Z]\d+[A-Z](?:\s))?(.+)$'

    }

    Process {

        $Command = "GetLog $Component"

        if(($Component -eq "VAULT") -or ($Component -eq "PADR")) {

            if($PSBoundParameters.ContainsKey("TimeFrom")) {

                $DateStamp = (Get-Date $($PSBoundParameters["TimeFrom"]) -Format ddMMyyyy:HHmm)
                $Command = "$Command /TimeFrom $DateStamp"

            }

        }

        if($Component -eq "VAULT") {

            if($PSBoundParameters.ContainsKey("Lines")) {

                $Command = "$Command /Lines $($PSBoundParameters["Lines"])"

            }

        }

        if(($Component -eq "ENE") -or ($Component -eq "CVM")) {

            $Command = "$Command /LogFile $($PSBoundParameters["LogFile"])"

        }

        $PSBoundParameters.Add("CommandParameters", "$Command")

        switch ($Component) {

            "PADR" {$Pattern = $PADR; break}

            "Vault" {$Pattern = $Vault; break}

            "ENE" {

                if ($($PSBoundParameters["LogFile"]) -eq "Console") {

                    $Pattern = $ENEConsole; break

                } elseif ($($PSBoundParameters["LogFile"]) -eq "Trace") {

                    $Pattern = $ENETrace; break

                }

            }

            default {$Pattern = '(.+)'; break}

        }

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            ($Result.StdOut).Split("`n") | ForEach-Object {

                $LogEntry = ($_ | Select-String $Pattern -AllMatches)

                if($LogEntry -match '\S') {

                    [PSCustomObject]@{

                        "Time"    = $LogEntry.Matches.Groups[1].Value -replace '(\s\W\s)', ' '
                        "Code"    = $LogEntry.Matches.Groups[2].Value
                        "Message" = $LogEntry.Matches.Groups[3].Value

                    } | Add-ObjectDetail -typename VaultControl.Log.Component

                }

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Get-PARServer {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [string]$PassFile

    )

    Process {

        Try {

            $ErrorActionPreference = "Stop"

            $PSBoundParameters["CommandParameters"] = "GetCPU"

            $CPU = Invoke-PARClient @PSBoundParameters

            $PSBoundParameters["CommandParameters"] = "GetDiskUsage"

            $Disk = Invoke-PARClient @PSBoundParameters

            $PSBoundParameters["CommandParameters"] = "GetMemoryUsage"

            $Memory = Invoke-PARClient @PSBoundParameters

            $PSBoundParameters["CommandParameters"] = "List"

            $Components = Invoke-PARClient @PSBoundParameters

            #Remove PARAgent version header line
            $CPU, $Disk, $Memory, $Components = $CPU, $Disk, $Memory, $Components | ForEach-Object {

                $_.StdOut -replace '(?m)^PARAgent version:.*(\r?\n)?', ''

            }

            If($CPU) {

                $CPU = ($CPU  | Select-String '(\d+\.\d+)' -AllMatches).Matches.Value

            }

            Else {$CPU = $null}

            If($Disk) {

                $Disk = $Disk | Select-String '(.+)' -AllMatches | ForEach-Object {

                    $DiskInfo = $_.Line.Split(" ")

                    For ($i = 0 ; $i -lt $DiskInfo.count ; $i += 3) {

                        $Drive = $DiskInfo[$i..($i + 3)]

                        [PSCustomObject]@{

                            "Drive"         = $Drive[0]
                            "FreeSpace(MB)" = ($Drive[1] | Select-String '(\d+)' -AllMatches).Matches.Value
                            "FreeSpace(%)"  = ($Drive[2] | Select-String '(\d+\.\d+)' -AllMatches).Matches.Value

                        }

                    }

                }

            } Else {$Disk = $null}

            If($Memory) {

                $Memory = ($Memory | Select-String '(.+)' -AllMatches).Matches.Value | ForEach-Object {

                    $MemoryInfo = $_.Split(":")

                    $MemoryData = ($MemoryInfo[1] | Select-String '(\S+)' -AllMatches).Matches.Value

                    [PSCustomObject]@{

                        "MemoryType" = ($MemoryInfo[0].Split(" "))[0]
                        "Total(K)"   = (($MemoryData[0]).Split("=")[1] |  Select-String '(\d+)' -AllMatches).Matches.Value
                        "Free(K)"    = (($MemoryData[1]).Split("=")[1] |  Select-String '(\d+)' -AllMatches).Matches.Value
                        "Used(%)"    = (($MemoryData[2]).Split("=")[1] | Select-String '(\d+\.\d+)' -AllMatches).Matches.Value

                    }

                }

            }

            Else {$Memory = $null}

            If($Components) {

                $Components = ($Components | Select-String '(.+)' -AllMatches).Matches.Value | ForEach-Object {

                    $Component = $_.Split(" ")

                    $Version = ($_ | Select-String "(\d+\D\d+\D\d+\D\d+)" -AllMatches).Matches.Value

                    [PSCustomObject]@{

                        Component = $Component[0]
                        Version   = $Version

                    }

                }

            }

            Else {$Components = $null}

            [PSCustomObject]@{

                "Server"     = $Server.ToUpper()
                "CPU(%)"     = $CPU
                "Disk"       = $Disk
                "Memory"     = $Memory
                "Components" = $Components

            }

        } Catch {throw $_}

    }

}

# .ExternalHelp VaultControl-help.xml
Function Get-PARServerLog {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Application", "Security", "System")]
        [string]$LogName,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [datetime]$TimeFrom = (Get-Date).AddDays(-1)
    )

    Begin {

        $Pattern = 'EventLogRecordTime:(.+\s)Source:(.+\s)Computer:(.+\s)Event ID:(.+\s)Event Type:(.+\s)Description:(.+(?:[\S\s]+?))(?=\Z|EventLogRecordTime)'

    }
    Process {

        $Command = "GetOSLog /Name $LogName /TimeFrom $(Get-Date $TimeFrom -Format ddMMyyyy:HHmm)"

        $PSBoundParameters.Add("CommandParameters", "$Command")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            $Logs = $Result.StdOut -replace '(?m)^PARAgent version:.*(\r?\n)?', '' | Select-String $Pattern -AllMatches

            $Logs | Select-Object -ExpandProperty Matches | ForEach-Object {

                [PSCustomObject]@{

                    "EventLogRecordTime" = ($_.Groups[1].Value).Trim()
                    "Source"             = ($_.Groups[2].Value).Trim()
                    "Computer"           = ($_.Groups[3].Value).Trim()
                    "EventID"            = [int]([int64](($_.Groups[4].Value).Trim()) -band 0xFFFF)
                    "EventType"          = ($_.Groups[5].Value).Trim()
                    "Description"        = ($_.Groups[6].Value).Trim()

                } | Add-ObjectDetail -typename VaultControl.Log.Server

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Get-PARService {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [string]$PassFile,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$ServiceName = "*"
    )

    Process {

        $ServiceName = $ServiceName -replace '(.+)', '\"$&\"'

        $PSBoundParameters.Add("CommandParameters", "ServiceStatus /ServiceName $ServiceName")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            ($Result.StdOut).Split("`n") | ForEach-Object {

                $Service = ($_ | Select-String '^(.+)is\s([a-z]+)' -AllMatches)

                If($Service -match '\S') {

                    [PSCustomObject]@{

                        "Server"  = $Result.Server
                        "Service" = $Service.Matches.Groups[1].Value
                        "Status"  = $($Service.Matches.Groups[2].Value).Substring(0, 1).ToUpper() + $($Service.Matches.Groups[2].Value).Substring(1)

                    }

                }

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function New-PARPassFile {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Path
    )

    Begin {

        If(-not $Script:PAR.ClientPath) {

            throw "PARClient.exe not found `nRun Set-PARConfiguration to set path to PARClient"

        }

    }

    Process {

        $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)

        $Folder = Split-Path -Path $FilePath -Parent

        If($PSCmdlet.ShouldProcess($FilePath, "Create PARClient Password File")) {

            If(-not (Test-Path -Path $Folder -PathType Container)) {

                $null = New-Item -Path $Folder -ItemType Directory -Force

            }

            $Process = Start-Process -FilePath $Script:PAR.ClientPath -ArgumentList "/CreatePassFile `"$FilePath`"" `
                -WorkingDirectory (Split-Path -Path $Script:PAR.ClientPath -Parent) -NoNewWindow -Wait -PassThru

            If(($Process.ExitCode -eq 0) -and (Test-Path -Path $FilePath -PathType Leaf)) {

                Get-Item -Path $FilePath

            } Else {

                Write-Error -Message "Failed to create password file $FilePath (Exit Code: $($Process.ExitCode))" -ErrorId "PassFileNotCreated"

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Restart-PARComponent {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "ShouldProcess handling is in Invoke-PARClient")]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR")]
        [string]$Component
    )

    Process {

        $PSBoundParameters.Add("CommandParameters", "Restart $Component")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            $Service = ($Result.StdOut | Select-String '(restarted|Error)' -AllMatches)

            [PSCustomObject]@{

                "Server"    = $Result.Server
                "Component" = $Component
                "Status"    = $($Service.Matches.Groups[1].Value).Substring(0, 1).ToUpper() + $($Service.Matches.Groups[1].Value).Substring(1)

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Restart-PARServer {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "ShouldProcess handling is in Invoke-PARClient")]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [string]$PassFile

    )

    Process {

        $PSBoundParameters.Add("CommandParameters", "REBOOT")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            [PSCustomObject]@{

                "Server" = $Result.Server
                "Status" = $Result.StdOut

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Set-PARComponentConfig {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "ShouldProcess handling is in Invoke-PARClient")]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR")]
        [string]$Component,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("DefaultTimeout", "MTU", "SecurityNotification", "DebugLevel", "DisableExceptionHandling",
            "EnableCheck", "EnableReplicate", "EnableFailover", "EnableDBSync", "FailoverMode")]
        [string]$Parameter,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Value,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Temporary", "Permanent", "Immediate")]
        [string]$Mode

    )

    Process {

        $PSBoundParameters.Add("CommandParameters", "SetParm $Component $Parameter=$Value /$Mode")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            $Update = ($Result.StdOut | Select-String '(success|Error)' -AllMatches)

            [PSCustomObject]@{

                "Server"    = $Result.Server
                "Component" = $Component
                "Parameter" = $($PSBoundParameters["Parameter"])
                "Value"     = $Value
                "Mode"      = $($PSBoundParameters["Mode"])
                "Status"    = $($Update.Matches.Groups[1].Value).Substring(0, 1).ToUpper() + $($Update.Matches.Groups[1].Value).Substring(1)
                "Message"   = $Result.StdOut

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Set-PARConfiguration {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateScript( {Test-Path $_ -PathType Leaf})]
        [ValidateNotNullOrEmpty()]
        [string]$ClientPath,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [int]$Port
    )

    Begin {

        $Defaults = [pscustomobject]@{}

    }

    Process {

        If($PSBoundParameters.Keys -contains "ClientPath") {

            $Defaults | Add-Member -MemberType NoteProperty -Name ClientPath -Value $ClientPath

        }

        If($PSBoundParameters.Keys -contains "Port") {

            $Defaults | Add-Member -MemberType NoteProperty -Name Port -Value $Port

        }

    }

    End {

        Set-Variable -Name PAR -Value $Defaults -Scope Script

        $Script:PAR | Select-Object -Property * | Export-Clixml -Path "$env:HOMEDRIVE$env:HomePath\PARConfiguration.xml" -Force

    }

}

# .ExternalHelp VaultControl-help.xml
Function Start-PARComponent {
    [CmdletBinding(SupportsShouldProcess)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "ShouldProcess handling is in Invoke-PARClient")]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR", "ENE", "CVM")]
        [string]$Component,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [switch]$Last
    )

    Process {

        $Command = "Start $Component"

        if($Component -eq "Vault") {

            if($PSBoundParameters.ContainsKey("Last")) {

                $Command = "$Command /Last"

            }

        }

        $PSBoundParameters.Add("CommandParameters", "$Command")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            $Service = ($Result.StdOut | Select-String '(started|Error)' -AllMatches)

            [PSCustomObject]@{

                "Server"    = $Result.Server
                "Component" = $Component
                "Status"    = $($Service.Matches.Groups[1].Value).Substring(0, 1).ToUpper() + $($Service.Matches.Groups[1].Value).Substring(1)

            }

        }

    }

}

# .ExternalHelp VaultControl-help.xml
Function Stop-PARComponent {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "ShouldProcess handling is in Invoke-PARClient")]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Password"
        )]
        [securestring]$Password,

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

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True,
            ParameterSetName = "PassFile"
        )]
        [string]$PassFile,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Vault", "PADR", "ENE", "CVM")]
        [string]$Component,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("Normal", "Immediate", "Terminate")]
        [string]$ShutdownMode
    )

    Process {

        $Command = "Stop $Component"

        If($Component -eq "Vault") {

            if($PSBoundParameters.ContainsKey("ShutdownMode")) {

                $Command = "$Command /$($PSBoundParameters["ShutdownMode"])"

            }

        }

        $PSBoundParameters.Add("CommandParameters", "$Command")

        $Result = Invoke-PARClient @PSBoundParameters

        If($Result.StdOut) {

            $Service = ($Result.StdOut | Select-String '(stopped|Error)' -AllMatches)

            [PSCustomObject]@{

                "Server"    = $Result.Server
                "Component" = $Component
                "Status"    = $($Service.Matches.Groups[1].Value).Substring(0, 1).ToUpper() + $($Service.Matches.Groups[1].Value).Substring(1)

            }

        }

    }

}

#Read config and make available in script scope
$ConfigFile = "$env:HOMEDRIVE$env:HomePath\PARConfiguration.xml"
If(Test-Path $ConfigFile) {
    Write-Verbose "Importing Settings: $ConfigFile"
    $config = Import-Clixml -Path $ConfigFile
    Set-Variable -Name PAR -Value $config -Scope Script
}