Autotask.psm1

<#
    .COPYRIGHT
    Copyright (c) Office Center Hønefoss AS. All rights reserved. Licensed under the MIT license.
    See https://github.com/officecenter/Autotask/blob/master/LICENSE.md for license information.
#>


[CmdletBinding()]
Param(
  [Parameter(
      Position = 0
  )]
  [pscredential]
  $Credential = $Global:AtwsCredential,
    
  [Parameter(
      Position = 1  
  )]
  [String]
  $ApiTrackingIdentifier = $Global:AtwsApiTrackingIdentifier,

  [Parameter(
      Position = 2,
      ValueFromRemainingArguments = $True
  )]
  [String[]]
  $EntityName = $Global:AtwsRefreshCachePattern
)

Write-Debug ('{0}: Start of module import' -F $MyInvocation.MyCommand.Name)


# Special consideration for -Verbose, as there is no $PSCmdLet context to check if Import-Module was called using -Verbose
# and $VerbosePreference is not inherited from Import-Module for some reason.

# Remove comments
$ParentCommand = ($MyInvocation.Line -split '#')[0]

# Store Previous preference
$OldVerbosePreference = $VerbosePreference
If ($ParentCommand -like '*-Verbose*') {
  Write-Debug ('{0}: Verbose preference detected. Verbose messages ON.' -F $MyInvocation.MyCommand.Name)
  $VerbosePreference = 'Continue'
}
$OldDebugPreference = $DebugPreference
If ($ParentCommand -like '*-Debug*') {
  Write-Debug ('{0}: Debug preference detected. Debug messages ON.' -F $MyInvocation.MyCommand.Name)
  $DebugPreference = 'Continue'
}

# Read our own manifest to access configuration data
$ManifestFileName = $MyInvocation.MyCommand.Name -replace 'pdm1$','psd1'
$ManifestDirectory = Split-Path $MyInvocation.MyCommand.Path -Parent

Write-Debug ('{0}: Loading Manifest file {1} from {2}' -F $MyInvocation.MyCommand.Name, $ManifestFileName, $ManifestDirectory)


Import-LocalizedData -BindingVariable My -FileName $ManifestFileName -BaseDirectory $ManifestDirectory


# Get all function files as file objects
# Private functions can only be called internally in other functions in the module

$PrivateFunction = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue ) 
Write-Debug ('{0}: Found {1} script files in {2}\Private' -F $MyInvocation.MyCommand.Name, $PrivateFunction.Count, $PSScriptRoot)

# Public functions will be exported with Prefix prepended to the Noun of the function name

$PublicFunction = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue ) 
Write-Debug ('{0}: Found {1} script files in {2}\Public' -F $MyInvocation.MyCommand.Name, $PublicFunction.Count, $PSScriptRoot)

# Static functions will be exported with Prefix prepended to the Noun of the function name

$StaticFunction = @( Get-ChildItem -Path $PSScriptRoot\Static\*.ps1 -ErrorAction SilentlyContinue ) 
Write-Debug ('{0}: Found {1} script files in {2}\Static' -F $MyInvocation.MyCommand.Name, $StaticFunction.Count, $PSScriptRoot)

# Static functions will be exported with Prefix prepended to the Noun of the function name

$DynamicFunction = @( Get-ChildItem -Path $PSScriptRoot\Dynamic\*.ps1 -ErrorAction SilentlyContinue ) 
Write-Debug ('{0}: Found {1} script files in {2}\Dynamic' -F $MyInvocation.MyCommand.Name, $DynamicFunction.Count, $PSScriptRoot)


Write-Verbose ('{0}: Importing {1} Private and {2} Public functions.' -F $MyInvocation.MyCommand.Name, $PrivateFunction.Count, $PublicFunction.Count)

# Loop through all script files and source them
foreach ($Import in @($PrivateFunction + $PublicFunction))
{
  Write-Debug ('{0}: Importing {1}' -F $MyInvocation.MyCommand.Name, $Import)
  try
  {
    . $Import.fullname
  }
  catch
  {
    throw "Could not import function $($Import.fullname): $_"
  }
}

# If they tried to pass any variables
If (($Credential) -or ($ApiTrackingIdentifier))
{
  Write-Verbose ('{0}: Credentials detected. Connecting to Autotask API' -F $MyInvocation.MyCommand.Name)
    
  # Remove Global variables (if used) for security
  If (Get-Variable -Name AtwsCredential -Scope Global -ErrorAction SilentlyContinue)
  {
    Write-Debug ('{0}: Credentials for {1} detected.' -F $MyInvocation.MyCommand.Name, $AtwsCredential.UserName)
    
    # Remove Global Object, credentials are now stored in a variable internal to the module
    
    Remove-Variable -Name AtwsCredential -Scope Global
  }
  
  If (Get-Variable -Name AtwsApiTrackingIdentifier -Scope Global -ErrorAction SilentlyContinue)
  {
    Write-Debug ('{0}: API tracking identifier for {1} detected.' -F $MyInvocation.MyCommand.Name, $AtwsCredential.UserName)
    
    # Remove Global Object, credentials are now stored in a variable internal to the module
    
    Remove-Variable -Name AtwsApiTrackingIdentifier -Scope Global
  }
  
  If (Get-Variable -Name AtwsRefreshCachePattern -Scope Global -ErrorAction SilentlyContinue)
  {
    Write-Debug ('{0}: Refreshing disk cache by force' -F $MyInvocation.MyCommand.Name)
    
    # Remove Global Object
    
    Remove-Variable -Name AtwsRefreshCachePattern -Scope Global
  }  
  
  If (Get-Variable -Name AtwsUsePicklistLabels -Scope Global -ErrorAction SilentlyContinue)
  {
    Write-Debug ('{0}: Converting picklistvalues to their labels are turned ON' -F $MyInvocation.MyCommand.Name)
    
    $Script:UsePickListLabels = $True
    
    # Remove Global Object
    
    Remove-Variable -Name AtwsUsePicklistLabels -Scope Global
  }
  
  If (Get-Variable -Name AtwsNoDiskCache -Scope Global -ErrorAction SilentlyContinue)
  {
    Write-Debug ('{0}: Force No disk cache detected. All functions are loaded from the scripts supplied by the module.' -F $MyInvocation.MyCommand.Name)
    Write-Verbose ('{0}: Force No disk cache detected. All functions are loaded from the scripts supplied by the module.' -F $MyInvocation.MyCommand.Name)
        
    # Remove Global Object, credentials are now stored in a variable internal to the module
    Remove-Variable -Name AtwsNoDiskCache -Scope Global
  
    # Connect to the API using required, additional parameters, using internal function name
    . Connect-AtwsWebServices -Credential $Credential -ApiTrackingIdentifier $ApiTrackingIdentifier -NoDiskCache
  }
  Else
  { 
    # Connect to the API using required, additional parameters, using internal function name
    . Connect-AtwsWebServices -Credential $Credential -ApiTrackingIdentifier $ApiTrackingIdentifier

  
    $DynamicCache = '{0}\WindowsPowershell\Cache\{1}\Dynamic' -f $([environment]::GetFolderPath('MyDocuments')), $Script:Atws.CI
    If (Test-Path $DynamicCache) {
      $FunctionCount = $DynamicFunction.Count
      $DynamicFunction = @( Get-ChildItem -Path $DynamicCache\*atws*.ps1 -ErrorAction SilentlyContinue )
      Write-Debug ('{0}: Personal disk cache: Found {1} script files in {2}' -F $MyInvocation.MyCommand.Name, $DynamicFunction.Count, $DynamicCache)
            
      $VersionString = "#Version {0}" -F $My.ModuleVersion
      $ScriptVersion = Select-String -Pattern $VersionString -Path $DynamicFunction.FullName
      If ($ScriptVersion.Count -ne $FunctionCount) {
        Write-Debug ('{0}: Personal disk cache: Wrong number of script files or scripts are not the right version in {1}, refreshing all entities.' -F $MyInvocation.MyCommand.Name, $DynamicCache)
        
        # Clear out old cache, it will be recreated
        $Null = Remove-Item -Path $DynamicFunction.fullname -Force -ErrorAction SilentlyContinue
        
        # Refresh ALL dynamic entities.
        $EntityName = '*' 
      }
    
      $OldFunctions = @(Get-ChildItem -Path $DynamicCache\*.ps1 -Exclude *Atws* -ErrorAction SilentlyContinue)
      If ($OldFunctions.Count -gt 0) {
      
        Write-Debug ('{0}: Personal disk cache: Found {1} old script files in {2}. Deleting.' -F $MyInvocation.MyCommand.Name, $OldFunctions.Count, $DynamicCache)
              
        $Null = Remove-Item -Path $OldFunctions.fullname -Force -ErrorAction SilentlyContinue
      }
    }
    Else {
      
      Write-Debug ('{0}: Personal disk cache {1} does not exist. Forcing load of all dynamic entities.' -F $MyInvocation.MyCommand.Name, $DynamicCache)
          
      # No personal dynamic cache. Refresh ALL dynamic entities.
      $EntityName = '*'
    }
   
    # Refresh any entities the caller has ordered
    # We only consider entities that are dynamic
    $Entities = Get-AtwsFieldInfo -Dynamic
    $EntitiesToProcess = @()
    
    Write-Debug ('{0}: {1} dynamic entities are eligible for refresh.' -F $MyInvocation.MyCommand.Name, $DynamicCache)
    
    Foreach ($String in $EntityName)
    {
      Write-Debug ('{0}: Selecting entities that match pattern "{1}"' -F $MyInvocation.MyCommand.Name, $String)
            
      $EntitiesToProcess += $Entities.GetEnumerator().Where({$_.Key -like $String})
    }
    # Prepare Index for progressbar
    $Index = 0
    $ProgressParameters = @{
      Activity = 'Updating diskcache for requested entities.'
      Id = 10
    }
    
    # Make sure we only check each possible entity once
    $EntitiesToProcess = $EntitiesToProcess | Sort-Object -Property Name -Unique
    
    Write-Debug ('{0}: {1} entities have been selected for refresh' -F $MyInvocation.MyCommand.Name, $EntitiesToProcess.Count)
        
    
    Foreach ($EntityToProcess in $EntitiesToProcess)
    {
      $Index++
      $PercentComplete = $Index / $EntitiesToProcess.Count * 100
      
      # Add parameters for @splatting
      $ProgressParameters['PercentComplete'] = $PercentComplete
      $ProgressParameters['Status'] = 'Entity {0}/{1} ({2:n0}%)' -F $Index, $EntitiesToProcess.Count, $PercentComplete
      $ProgressParameters['CurrentOperation'] = 'Getting fieldinfo for {0}' -F $EntityToProcess.Name
      
      Write-Progress @ProgressParameters
      
      $null = Get-AtwsFieldInfo -Entity $EntityToProcess.Key -UpdateCache
    }
    
    If ($EntitiesToProcess.Count -gt 0)
    { 
      Write-Debug ('{0}: Calling Import-AtwsCmdLet with {1} entities to process' -F $MyInvocation.MyCommand.Name, $EntitiesToProcess.Count)
        
      # Recreate functions that have been updated
      Import-AtwsCmdLet -Entities $EntitiesToProcess
    
      # Re-read Dynamic functions
      $DynamicFunction = @( Get-ChildItem -Path $DynamicCache\*atws*.ps1 -ErrorAction SilentlyContinue ) 
    
      Write-Debug ('{0}: Personal disk cache: Found {1} script files in {2}' -F $MyInvocation.MyCommand.Name, $DynamicFunction.Count, $DynamicCache)
    }    
  }

  Write-Verbose ('{0}: Importing {1} Static and {2} Dynamic functions.' -F $MyInvocation.MyCommand.Name, $StaticFunction.Count, $DynamicFunction.Count)
  
  # Loop through all script files and source them
  foreach ($Import in @($StaticFunction + $DynamicFunction))
  {
    Write-Debug ('{0}: Importing {1}' -F $MyInvocation.MyCommand.Name, $Import)

    try
    {
      . $Import.fullname
    }
    catch
    {
      throw "Could not import function $($Import.fullname): $_"
    }
  }
  
  # Explicitly export public functions
  Write-Debug ('{0}: Exporting {1} Public functions.' -F $MyInvocation.MyCommand.Name, $PublicFunction.Count) 
  Export-ModuleMember -Function $PublicFunction.Basename

  # Explicitly export static functions
  Write-Debug ('{0}: Exporting {1} Static functions.' -F $MyInvocation.MyCommand.Name, $StaticFunction.Count)
  Export-ModuleMember -Function $StaticFunction.Basename

  # Explicitly export dynamic functions
  Write-Debug ('{0}: Exporting {1} Dynamic functions.' -F $MyInvocation.MyCommand.Name, $DynamicFunction.Count)
  Export-ModuleMember -Function $DynamicFunction.Basename
}
Else {
  Write-Verbose 'No Credentials were passed with -ArgumentList. Loading module without any connection to Autotask Web Services. Use Connect-AtwsWebAPI to connect.'
  Export-ModuleMember -Function 'Connect-AtwsWebAPI'
}


# Backwards compatibility since we are now trying to use consistent naming
Set-Alias -Scope Global -Name 'Connect-AutotaskWebAPI' -Value 'Connect-AtwsWebAPI'


# Restore Previous preference
If ($OldVerbosePreference -ne $VerbosePreference) {
  Write-Debug ('{0}: Restoring old Verbose preference' -F $MyInvocation.MyCommand.Name)
  $VerbosePreference = $OldVerbosePreference
}
If ($OldDebugPreference -ne $DebugPreference) {
  Write-Debug ('{0}: Restoring old Debug preference' -F $MyInvocation.MyCommand.Name)
  $DebugPreference = $OldDebugPreference
}