UcsConfig.psm1

<###### UCS Configuration Utilities ######>

<#### PARAMETERS ####>
$Script:LocalStorageLocation = "$env:LOCALAPPDATA\UCS"
$Script:CredentialFileName = 'UcsCredential.xml'
$Script:ConfigFileName = 'UcsConfig.xml'
$Script:DisabledSuffix = '-disabled'
$Script:ConfigPath = Join-Path $Script:LocalStorageLocation $Script:ConfigFileName
$Script:CredentialPath = Join-Path $Script:LocalStorageLocation $Script:CredentialFileName
$Script:ImportedConfigInUse = $false
$Script:ImportedCredentialInUse = $false

# Storage initilization at end, after function definitions.

<#### Function definitions ####>

<## INTERNAL ##>
Function New-UcsConfig
{
  <#
      .NOTES
      For internal use only - used to create initial config objects.
 
      .PARAMETER Priority
      The item with the lowest numerical value priority goes first in order. In case of a tie, APIs are ranked alphabetically.
  #>

  Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidateSet('REST','SIP','Poll','Push','Web','Provisioning')][String]$API,
    [Nullable[Timespan]]$Timeout = (New-TimeSpan -Seconds 3),
    [Nullable[Int]][ValidateRange(1,100)]$Retries = 2,
    [Nullable[Int]][ValidateRange(0,65535)]$Port = 80,
    [Nullable[bool]]$EnableEncryption = $false,
    [Nullable[Int]]$Priority = 50,
    [Nullable[bool]]$Enabled = $true
  )

  if($Timeout.TotalSeconds -le 0)
  {
    Write-Error "Couldn't create options because timeout was set to 0 or less." -ErrorAction Stop -Category InvalidArgument
  }

  $OutputObject = $API | Select-Object @{Name='API';Expression={$API}},
    @{Name='Timeout';Expression={$Timeout}},
    @{Name='Retries';Expression={$Retries}},
    @{Name='Port';Expression={$Port}},
    @{Name='EnableEncryption';Expression={$EnableEncryption}},
    @{Name='Priority';Expression={$Priority}},
    @{Name='Enabled';Expression={$Enabled}}

  Return $OutputObject
}

Function Get-UcsConfig
{
  Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidateSet('REST','SIP','Poll','Push','Web','Provisioning')][String]$API
  )

  $RequestedConfig = $Script:MasterConfig | Where-Object -Property API -EQ -Value $API

  Return $RequestedConfig
}

Function Get-UcsConfigPriority
{
  $AllConfigs = $Script:MasterConfig
  $EnabledConfigs = $AllConfigs | Where-Object -Property Enabled -EQ -Value $true
  $SortedConfigs = $EnabledConfigs | Sort-Object -Property Priority,API
  $SortedConfigNames = $SortedConfigs | Select-Object -ExpandProperty API

  Return $SortedConfigNames
}

Function Set-UcsConfig
{
  Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidateSet('REST','SIP','Poll','Push','Web','Provisioning')][String]$API,
    [Nullable[Timespan]]$Timeout = $null,
    [Nullable[Int]][ValidateRange(1,100)]$Retries = $null,
    [Nullable[Int]][ValidateRange(0,65535)]$Port = $null,
    [Nullable[bool]]$EnableEncryption = $null,
    [Nullable[Int]]$Priority = $null,
    [Nullable[bool]]$Enabled = $null
  )

  Process
  {
      $WorkingConfig = Get-UcsConfig -API $API

      if($null -ne $Retries)
      {
        $WorkingConfig.Retries = $Retries
      }

      if($null -ne $Timeout)
      {
        if($Timeout.TotalSeconds -lt 0)
        {
          Write-Error "Couldn't create options because timeout was set to less than 0." -ErrorAction Stop -Category InvalidArgument
        }
        else
        {
          $WorkingConfig.Timeout = $Timeout
        }
      }

      if($null -ne $Port)
      {
        $WorkingConfig.Port = $Port
      }

      if($null -ne $EnableEncryption)
      {
        if($null -eq $WorkingConfig.EnableEncryption)
        {
          Write-Error ('Encryption is not supported by the {0} API.' -f $API)
        }
        else
        {
          $WorkingConfig.EnableEncryption = $EnableEncryption
        }
      }

      if($null -ne $Priority)
      {
        $MatchingPriority = $Script:MasterConfig | Where-Object -Property Priority -eq $Priority | Measure-Object
        if($MatchingPriority.Count -gt 0)
        {
          Write-Warning ('Cannot set priority of {0} to {1} because that priority level is in use on one or more other APIs.' -f $WorkingConfig.API,$Priority)
        }
        else
        {
          $WorkingConfig.Priority = $Priority
        }
      }

      if($null -ne $Enabled)
      {
        $WorkingConfig.Enabled = $Enabled
      }

      Foreach($Configuration in $Script:MasterConfig)
      {
        if($Configuration.API -eq $API)
        {
         $Configuration = $WorkingConfig
        }
      }
  }
  End
  {
     Update-UcsConfigStorage
  }
}

Function New-UcsConfigCredential
{
  Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidateSet('REST','Poll','Push','Web')][String]$API,
    [Parameter(Mandatory)][PSCredential]$Credential,
    [String]$DisplayName = '',
    [Int]$Priority = 50,
    [String]$Identity = '*',
    [Boolean]$Enabled = $true
  )

  $OutputObject = $API | Select-Object @{Name='API';Expression={$API}},
    @{Name='Identity';Expression={$Identity}},
    @{Name='DisplayName';Expression={$DisplayName}},
    @{Name='Credential';Expression={$Credential}},
    @{Name='Priority';Expression={$Priority}},
    @{Name='Enabled';Expression={$Enabled}}


  Add-UcsConfigCredential $OutputObject
}

Function New-UcsConfigCredentialPlaintext
{
  [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", 'Password',
    Justification='This cmdlet takes a plaintext password and converts it to SecureString.')]
  [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", '',
    Justification='This cmdlet takes a plaintext password and converts it to SecureString.')]
  Param (
    [Parameter(Mandatory)][String]$Username,
    [Parameter(Mandatory)][String]$Password
  )

  [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", '',
    Scope='Function')] #TODO: This doesn't successfully suppress the error. Why?
  $SecureStringPassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
  $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($Username,$SecureStringPassword)

  Return $Credential
}

Function Get-UcsConfigCredential
{
  Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline)][ValidateSet('REST','Poll','Push','Web')][String]$API,
    [Switch]$IncludeDisabled,
    [Switch]$CredentialOnly
  )

  $AllCredentials = $Script:MasterCredentials

  if(!$IncludeDisabled)
  {
    $AllCredentials = $AllCredentials | Where-Object -Property Enabled -EQ -Value $true
  }

  $ThisAPICredentials = $AllCredentials | Where-Object -Property API -EQ -Value $API
  $SortedCredentials = $ThisAPICredentials | Sort-Object -Property Priority,API,Index

  if($CredentialOnly)
  {
    Return $SortedCredentials.Credential
  }
  else
  {
    Return $SortedCredentials
  }
}

Function Add-UcsConfigCredential
{
  [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", 'UcsConfigCredential',
  Justification='The type of object this accepts contains a PsCredential object.')]
  Param (
    [Parameter(Mandatory)][Object]$UcsConfigCredential
  )
  Process
  {
      if($UcsConfigCredential.Credential.GetType().Name -ne 'PSCredential' -or $null -eq $UcsConfigCredential.Priority)
      {
        Write-Error "Invalid UcsConfigCredential supplied."
      }

      $HighestIndex = $Script:MasterCredentials | Sort-Object -Property Index -Descending `
        | Select-Object -First 1 | Select-Object -ExpandProperty Index
      $ThisIndex = $HighestIndex + 1

      $IndexRemoved = $UcsConfigCredential | Select-Object -Property * -ExcludeProperty Index
      $CredentialToSave = $IndexRemoved | Select-Object -Property *,@{Name='Index';Expression={$ThisIndex}}

      $null = $Script:MasterCredentials.Add($CredentialToSave)
  }
  End
  {
    Update-UcsConfigCredentialStorage
  }
}

Function Remove-UcsConfigCredential
{
  Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)][Int[]]$Index
  )

  Process
  {
    Foreach($ThisIndex in $Index)
    {
      #We must rebuild the arraylist because doing a simple filter on the arraylist turns it into a collection of fixed size.
      $NewMasterCredentials = New-Object Collections.ArrayList
      Foreach($Credential in $Script:MasterCredentials)
      {
        if($Credential.Index -ne $ThisIndex)
        {
          $null = $NewMasterCredentials.Add($Credential)
        }
      }
    }
  }
  End
  {
    $Script:MasterCredentials = $NewMasterCredentials
    Update-UcsConfigCredentialStorage
  }
}

Function Set-UcsConfigCredential
{
   Param (
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)][Int[]]$Index,
    [AllowEmptyString()][ValidateSet('REST','Poll','Push','Web')][String]$API = '',
    [PsCredential]$Credential = $null,
    [String]$DisplayName = '',
    [Nullable[Int]]$Priority = $null,
    [String]$Identity = '',
    [Nullable[Boolean]]$Enabled = $null
  )

  Process
  {
    Foreach($ThisIndex in $Index)
    {
      $WorkingCredential = $Script:MasterCredentials | Where-Object -Property Index -EQ -Value $ThisIndex

      if($null -eq $WorkingCredential)
      {
        Write-Error "Invalid index $ThisIndex."
        Continue
      }

      if($API.Length -gt 0)
      {
        $WorkingCredential.API = $API
      }

      if($null -ne $Credential)
      {
        if($Credential.GetType().Name -eq 'PSCredential')
        {
          $WorkingCredential.Credential = $Credential
        }
      }

      if($DisplayName.Length -gt 0)
      {
        $WorkingCredential.DisplayName = $DisplayName
      }

      if($null -ne $Priority)
      {
        $WorkingCredential.Priority = $Priority
      }

      if($Identity.Length -gt 0)
      {
        $WorkingCredential.Identity = $Identity
      }

      if($null -ne $Enabled)
      {
        $WorkingCredential.Enabled = $Enabled
      }

      Foreach($Credential in $Script:MasterCredentials)
      {
        if($Credential.Index -eq $ThisIndex)
        {
          $Credential = $WorkingCredential
          Break
        }
      }
    }
  }
  End
  {
    Update-UcsConfigCredentialStorage
  }
}


<## Config storage ##>
Function Import-UcsConfigStorage
{
  Param (
    [String]$Path = $Script:ConfigPath
  )

  if((Test-Path $Path) -eq $false)
  {
    Write-Error "Could not find file at $Path." -ErrorAction Stop
  }

  Write-Debug "Importing $Path to UcsConfig."
  $Imported = Import-Clixml -Path $Path

  $Script:MasterConfig = $Imported
  $Script:ImportedConfigInUse = $true
}

Function Update-UcsConfigStorage
{
  Param (
    [String]$Path = $Script:ConfigPath
  )

  $Directory = Split-Path $Path

  if( (Test-Path $Directory) -eq $false)
  {
    Write-Debug "Path $Directory not found. Creating..."
    $null = New-Item -Path $Directory -ItemType Directory -Force
  }

  if($Script:ImportedConfigInUse)
  {
    Write-Debug "Writing XML file to $Path."
    $Script:MasterConfig | Export-Clixml -Path $Path -Depth 1
  }
  else
  {
    Write-Debug "Imported config not currently in use, not updating."
  }
}

Function Enable-UcsConfigStorage
{
   [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
   Param()

   if($Script:ImportedConfigInUse -eq $true)
   {
    Write-Error "Config storage already in use."
    Break
   }

   if($PSCmdlet.ShouldProcess($Script:ConfigPath))
   {
     $Script:ImportedConfigInUse = $true
     Update-UcsConfigStorage
   }
}

Function Disable-UcsConfigStorage
{
   [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
   Param()

   if($Script:ImportedConfigInUse -ne $true)
   {
    Write-Error "Config storage not in use."
    Break
   }

   if($PSCmdlet.ShouldProcess($Script:ConfigPath))
   {
     $Script:ImportedConfigInUse = $false
     $ThisItem = Get-Item -Path $Script:ConfigPath
     $NewName = ('{0}{1}{2}' -f $ThisItem.BaseName,$Script:DisabledSuffix,$ThisItem.Extension)
     $NewPath = Join-Path $ThisItem.Directory $NewName
     $null = Rename-Item -Path $Script:ConfigPath -NewName $NewPath -Force
   }
}

Function Get-UcsConfigStorageIsEnabled
{
  Return $Script:ImportedConfigInUse
}

<## Credential storage ##>
Function Import-UcsConfigCredentialStorage
{
  Param (
    [String]$Path = $Script:CredentialPath
  )

  if((Test-Path $Path) -eq $false)
  {
    Write-Error "Could not find file at $Path." -ErrorAction Stop
  }

  Write-Debug "Importing $Path to UcsConfig."
  $Imported = Import-Clixml -Path $Path

  $Script:MasterCredentials = New-Object Collections.ArrayList
  Foreach($Cred in $Imported)
  {
    $null = $Script:MasterCredentials.Add($Cred)
  }

  $Script:ImportedCredentialInUse = $true
}

Function Update-UcsConfigCredentialStorage
{
  Param (
    [String]$Path = $Script:CredentialPath
  )

  $Directory = Split-Path $Path

  if( (Test-Path $Directory) -eq $false)
  {
    Write-Debug "Path $Directory not found. Creating..."
    $null = New-Item -Path $Directory -ItemType Directory -Force
  }

  if($Script:ImportedCredentialInUse)
  {
    Write-Debug "Writing XML file to $Path."
    $Script:MasterCredentials | Export-Clixml -Path $Path -Depth 1
  }
  else
  {
    Write-Debug "Imported credentials not currently in use, not updating."
  }
}

Function Enable-UcsConfigCredentialStorage
{
   [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
   Param()

   if($Script:ImportedCredentialInUse -eq $true)
   {
    Write-Error "Credential storage already in use."
    Break
   }

   if($PSCmdlet.ShouldProcess($Script:CredentialPath))
   {
     $Script:ImportedCredentialInUse = $true
     Update-UcsConfigCredentialStorage
   }
}

Function Disable-UcsConfigCredentialStorage
{
   [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
   Param()

   if($Script:ImportedCredentialInUse -ne $true)
   {
    Write-Error "Credential storage not in use."
    Break
   }

   if($PSCmdlet.ShouldProcess($Script:CredentialPath))
   {
     $Script:ImportedCredentialInUse = $false
     $ThisItem = Get-Item -Path $Script:CredentialPath
     $NewName = ('{0}{1}{2}' -f $ThisItem.BaseName,$Script:DisabledSuffix,$ThisItem.Extension)
     $NewPath = Join-Path $ThisItem.Directory $NewName
     $null = Rename-Item -Path $Script:CredentialPath -NewName $NewPath -Force
   }
}
Function Get-UcsConfigCredentialStorageIsEnabled
{
  Return $Script:ImportedCredentialInUse
}

<#### Create Credential Storage ####>
$Script:MasterCredentials = New-Object Collections.ArrayList

<#### Initialize default credentials ####>
New-UcsConfigCredential -API REST -Credential (New-UcsConfigCredentialPlaintext -Username 'Polycom' -Password '456') -DisplayName "Polycom default REST credential" -Priority 1000
New-UcsConfigCredential -API Web -Credential (New-UcsConfigCredentialPlaintext -Username 'Polycom' -Password '456') -DisplayName "Polycom default Web credential" -Priority 1000
New-UcsConfigCredential -API Poll -Credential (New-UcsConfigCredentialPlaintext -Username 'UCSToolkit' -Password 'UCSToolkit') -DisplayName "Script default Polling credential" -Priority 1000
New-UcsConfigCredential -API Push -Credential (New-UcsConfigCredentialPlaintext -Username 'UCSToolkit' -Password 'UCSToolkit') -DisplayName "Script default Push credential" -Priority 1000

<#### Define defaults for configs ####>
$Script:MasterConfig = (
  (New-UcsConfig -API REST -Timeout (New-TimeSpan -Seconds 2) -Retries 1 -Port 80 -EnableEncryption $false -Priority 1 -Enabled $true),
  (New-UcsConfig -API SIP -Timeout (New-TimeSpan -Seconds 5) -Retries 2 -Port 5060 -EnableEncryption $null -Priority 90 -Enabled $true),
  (New-UcsConfig -API Poll -Timeout (New-TimeSpan -Seconds 2) -Retries 2 -Port 80 -EnableEncryption $null -Priority 30 -Enabled $true),
  (New-UcsConfig -API Push -Timeout (New-TimeSpan -Seconds 2) -Retries 2 -Port 80 -EnableEncryption $false -Priority 40 -Enabled $true),
  (New-UcsConfig -API Web -Timeout (New-TimeSpan -Seconds 2) -Retries 2 -Port 80 -EnableEncryption $null -Priority 20 -Enabled $true),
  (New-UcsConfig -API Provisioning -Timeout (New-TimeSpan -Seconds 5) -Retries 1 -Port 0 -EnableEncryption $null -Priority 100 -Enabled $true)
)

<#### Check for preferences ####>
if( Test-Path $Script:ConfigPath )
{
  Write-Debug "Found config file at $Script:ConfigPath."
  Import-UcsConfigStorage
}

if( Test-Path $Script:CredentialPath )
{
  Write-Debug "Found credential file at $Script:CredentialPath."
  Import-UcsConfigCredentialStorage
}