Public/Set-AtwsModuleConfiguration.ps1

<#
    .COPYRIGHT
    Copyright (c) ECIT Solutions AS. All rights reserved. Licensed under the MIT license.
    See https://github.com/ecitsolutions/Autotask/blob/master/LICENSE.md for license information.

#>


Function Set-AtwsModuleConfiguration {
    <#
        .SYNOPSIS
            This function updates the runtime configuration of the module.
        .DESCRIPTION
            This function updates the runtime configuration of the module. Values that can be changed while the module is loaded
            are:
            - Credentials (both username and password may be changed separately)
            - API key
            - Whether parameters with picklist values should show labels in place of numbers (ConvertPicklistIdsToLabel)
            - Debug preference
            - Verbose preference
            - Errorlimit - how many errors should Set-Atws* or New-Atws* functions accept before the operation is aborted

            The parameters Prefix and RefreshCache does not have any effect on the current connection. They must be saved and loaded
            as part of a later connection to have any effect.
        .INPUTS
            Nothing.
        .OUTPUTS
            Nothing.
        .EXAMPLE
            Set-AtwsModuleConfiguration -Credential $Credential -ApiTrackingIdentifier $string
        .NOTES
            NAME: Set-AtwsModuleConfiguration
            .LINK
            Get-AtwsModuleConfiguration
            New-AtwsModuleConfiguration
            Remove-AtwsModuleConfiguration
            Save-AtwsModuleConfiguration

    #>

    
    [cmdletbinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium',
        DefaultParameterSetName = 'Username_and_password'
    )]
    [Alias('Set-AtwsProfile')]
    Param
    ( 
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateNotNullOrEmpty()] 
        [pscredential]
        $Credential,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [ValidateNotNullOrEmpty()] 
        [string]
        $Username,
    
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $SecurePassword,
    
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $SecureTrackingIdentifier,
    
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [switch]
        $ConvertPicklistIdToLabel,
    
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateScript( {
                # It can be empty, but if it isn't it should be max 8 characters and only letters and numbers
                if ($_.length -eq 0 -or ($_ -match '[a-zA-Z0-9]' -and $_.length -gt 0 -and $_.length -le 8)) {
                    $true
                }
                else {
                    $false
                }
            })]
        [string]
        $Prefix,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [switch]
        $RefreshCache,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateSet('Stop', 'Inquire', 'Continue', 'SilentlyContinue')]
        [string]
        $DebugPref,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateSet('Stop', 'Inquire', 'Continue', 'SilentlyContinue')]
        [string]
        $VerbosePref,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateRange(0, 100)]
        [int]
        $ErrorLimit,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ArgumentCompleter( {
                param($Cmd, $Param, $Word, $Ast, $FakeBound)
                $(Get-ChildItem -Path $(Split-Path -Parent $profile) -Filter "*.clixml").FullName
            })]
        [ValidateScript( { 
                Test-Path $_
            })]
        [IO.FileInfo]
        $Path = $(Join-Path -Path $(Split-Path -Parent $profile) -ChildPath AtwsConfig.clixml),

        # Use this parameter to save to another configuration name.
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ArgumentCompleter( {
                param($Cmd, $Param, $Word, $Ast, $FakeBound)
                if (Test-Path $FakeBound.Path) {
                    [IO.FileInfo]$filepath = $FakeBound.Path
                }
                else {
                    [IO.FileInfo]$filepath = $(Join-Path -Path $Global:AtwsModuleConfigurationPath -ChildPath AtwsConfig.clixml)
                }
                $tempsettings = Import-Clixml -Path $filepath.Fullname
                if ($tempsettings -is [hashtable]) {
                    $tempsettings.keys
                }
            })]
        [ValidateNotNullOrEmpty()] 
        [String]
        $Name = 'Default',

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateSet('Disabled', 'Inline', 'LabelField')]
        [string]
        $PickListExpansion = 'LabelField',

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ValidateSet('Disabled', 'Inline', 'Hashtable')]
        [string]
        $UdfExpansion = 'Inline',

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Username_and_password'
        )]
        [Parameter(
            ParameterSetName = 'Credentials'
        )]
        [ArgumentCompleter( {
                param($Cmd, $Param, $Word, $Ast, $FakeBound)
                'Disabled'
                'Local'
                (Get-TimeZone -ListAvailable).Id | Sort-Object
            })]
        [ValidateScript( {
                # Allow disabled and local before testing timezone conversion
                if ($_ -in 'Disabled', 'Local') { return $true }
                # Allow any valid TimeZone on current system
                try { $null = [System.Timezoneinfo]::FindSystemTimeZoneById($_) }
                catch { return $false }
                return $true
            })]
        [string]
        $DateConversion = 'Local', 

        [switch]
        $PassThru
    )
    
    begin { 
    
        # Enable modern -Debug behavior
        if ($PSCmdlet.MyInvocation.BoundParameters['Debug'].IsPresent) {
            $DebugPreference = 'Continue' 
        }
        else {
            # Respect configured preference
            $DebugPreference = $Script:Atws.Configuration.DebugPref
        }
    
        Write-Debug ('{0}: Begin of function' -F $MyInvocation.MyCommand.Name)

        if (!($PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent)) {
            # No local override of central preference. Load central preference
            $VerbosePreference = $Script:Atws.Configuration.VerbosePref
        }
    
    }
  
    process {

        # Read existing configuration from disk
        if (Test-Path $Path) {
            Try { 
                # Try to save to the path
                $settings = Import-Clixml -Path $Path.Fullname
            }
            catch {
                $message = "{0}`nStacktrace:`n{1}" -f $_, $_.ScriptStackTrace
                throw (New-Object System.Configuration.Provider.ProviderException $message)
        
                return
            }
        }

        # Create an empty setting table
        if (-not ($settings -is [hashtable])) {
            $settings = @{}
        }

        # Get current configuration
        if ($Script:Atws.integrationsValue) {
            # We are connected. Use active configuration.
            $configuration = $Script:Atws.Configuration
        }
        # Not connected. Do we have an existing configuration from disk with this name?
        elseIf ($settings.containskey($Name)) {
            # Use saved configuration
            $configuration = $settings[$Name]
        }
        else {
            $message = "Not connected and no configuration by name '{0}' exists. Create a new configuration with New-AtwsModuleConfiguration. You may save it using Save-AtwsModuleConfiuguration." -f $Name
            throw (New-Object System.Configuration.Provider.ProviderException $message)
        
            return           
        }

        # Only run code if parameter has been used
        foreach ($parameter in $PSBoundParameters.GetEnumerator()) { 

            $caption = $MyInvocation.MyCommand.Name
            $verboseDescription = '{0}: Setting {1} to {2} as requested.' -F $caption, $parameter.key, $parameter.value
            $verboseWarning = '{0}: About to set {1} to {2}. Do you want to continue?' -F $caption, $parameter.key, $parameter.value

            if ($PSCmdlet.ShouldProcess($verboseDescription, $verboseWarning, $caption)) {
                # Only run code if parameter has been used
                switch ($parameter.key) { 
                    'Credential' {
                        $configuration.Username = $Credential.UserName
                        $configuration.SecurePassword = $Credential.Password
                    }
                    'Username' {
                        $configuration.Username = $UserName
                    }
                    'SecurePassword' {
                        $configuration.SecurePassword = $SecurePassword
                    }
                    'ApiTrackingIdentifier' { 
                        $configuration.SecureTrackingIdentifier = $SecureTrackingIdentifier
                    }
                    'ConvertPicklistIdToLabel' {
                        $configuration.ConvertPicklistIdToLabel = $ConvertPicklistIdToLabel.IsPresent
                        if ($ConvertPicklistIdToLabel.IsPresent) {
                            Add-Member -InputObject $configuration -MemberType NoteProperty -Name PicklistExpansion -Value 'Inline' -Force
                        }
                    }
                    'RefreshCache' { 
                        $configuration.RefreshCache = $RefreshCache.IsPresent
                    }
                    'DebugPref' { 
                        $configuration.DebugPref = $DebugPref
                    }
                    'VerbosePref' {
                        $configuration.VerbosePref = $VerbosePref
                    }
                    'ErrorLimit' {
                        $configuration.ErrorLimit = $ErrorLimit
                    }
                    'PicklistExpansion' {
                        Add-Member -InputObject $configuration -MemberType NoteProperty -Name PicklistExpansion -Value $PicklistExpansion -Force
                    }
                    'UdfExpansion' {
                        Add-Member -InputObject $configuration -MemberType NoteProperty -Name UdfExpansion -Value $UdfExpansion -Force
                    }
                    'DateConversion' {
                        Add-Member -InputObject $configuration -MemberType NoteProperty -Name DateConversion -Value $DateConversion -Force
                    }
                }
            }
        }

        # Are there any required properties where we have default values that does not have a value?
        $requiredProperties = @(
            'DebugPref', 'VerbosePref', 'ErrorLimit', 'PicklistExpansion', 'UdfExpansion', 'DateConversion'
        )
        foreach ($p in $requiredProperties) {
            if ($configuration.PSObject.Properties.name -notcontains $p) {
                $value = Get-Variable -Name $p -ValueOnly
                Add-Member -InputObject $configuration -MemberType NoteProperty -Name $p -Value $value -Force
            }
        }

        # Are we connected? Update current settings
        if ($Script:Atws.integrationsValue) {
            $Script:Atws.Configuration = $configuration

            # Prepare securestring password to be converted to plaintext
            $SecurePasswordString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($configuration.SecurePassword)
            
            $Script:Atws.ClientCredentials.UserName | Add-Member -Force -NotePropertyName UserName -NotePropertyValue $configuration.Username
            
            $BSTRstring = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($SecurePasswordString)
            $Script:Atws.ClientCredentials.UserName | Add-Member -Force -NotePropertyName Password -NotePropertyValue $BSTRstring

            # Set the integrationcode property to the API tracking identifier provided by the user
            $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Configuration.SecureTrackingIdentifier)
            $Script:Atws.IntegrationsValue.IntegrationCode = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
        }
        else {
            # We loaded these settings from disk. Save to disk again.
            Save-AtwsModuleConfiguration -Name $Name -Configuration $configuration
        }
    }
  
    end {
        Write-Debug ('{0}: End of function' -F $MyInvocation.MyCommand.Name)
        #TODO: Introduce PipelineSupport from Get-, -Set, -New, and Save-AtwsModuleConfiguration. Not doing this for now as it works as it is, jsut requires a few more lines.
        if ($PassThru.IsPresent) { 
            return $configuration
        }
    }
 
}