Autotask.psm1
<# .COPYRIGHT Copyright (c) Hugo Klemmestad. All rights reserved. Licensed under the MIT license. See https://github.com/ecitsolutions/Autotask/blob/master/LICENSE.md for license information. #> [CmdletBinding( PositionalBinding = $false )] Param( [Parameter( Position = 0 )] [pscustomobject] $Credential, [Parameter( Position = 1 )] [string] $ApiTrackingIdentifier, [Parameter( Position = 2, ValueFromRemainingArguments = $true )] [string[]] $entityName ) 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 # Add module path to manifest variable $My['ModuleBase'] = $manifestDirectory # The location $profile is only available on desktop and possibly Azure Runbooks. Not on # Azure Functions. Find a valid location for a configuration profile if ($profile) { # Use $profile if it exsist $Global:AtwsModuleConfigurationPath = $(Split-Path -Parent $profile) New-Variable -Name AtwsModuleTest -Value $(Split-Path -Parent $profile) -Option Constant } elseIf ($env:TEMP) { # Use $temp. The file will most likely never be used if not on desktop anyway $Global:AtwsModuleConfigurationPath = $env:TEMP } elseIf ($env:TMPDIR) { # Use $temp. The file will most likely never be used if not on desktop anyway $Global:AtwsModuleConfigurationPath = $env:TMPDIR } else { # Use $temp. The file will most likely never be used if not on desktop anyway $Global:AtwsModuleConfigurationPath = $env:PWD } # Make sure default profile path exists $ProfilePath = Join-Path -Path $Global:AtwsModuleConfigurationPath -ChildPath AtwsConfig.clixml if (-not (Test-Path $ProfilePath)) { Export-Clixml -InputObject @{} -Path $ProfilePath } # 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) # Entity functions will be exported with Prefix prepended to the Noun of the function name $entityFunction = @( Get-ChildItem -Path $PSScriptRoot\Functions\*.ps1 -ErrorAction SilentlyContinue ) Write-Debug ('{0}: Found {1} script files in {2}\Functions' -F $MyInvocation.MyCommand.Name, $entityFunction.Count, $PSScriptRoot) Write-Verbose ('{0}: Importing {1} Private, {2} Public functions and {3} entity functions.' -F $MyInvocation.MyCommand.Name, $privateFunction.Count, $publicFunction.Count, $entityFunction.count) # Loop through all supporting script files and source them foreach ($import in @($privateFunction + $publicFunction + $entityFunction)) { 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-Verbose ('{0}: Exporting {1} Public functions.' -F $MyInvocation.MyCommand.Name, $publicFunction.Count) Export-ModuleMember -Function $publicFunction.Basename # Explicitly export entity functions Write-Verbose ('{0}: Exporting {1} Entity functions.' -F $MyInvocation.MyCommand.Name, $publicFunction.Count) Export-ModuleMember -Function $entityFunction.Basename # Set to $true for explicit export of private functions. For debugging purposes only if ($false){ # Explicitly export private functions Write-Verbose ('{0}: Exporting {1} Private functions.' -F $MyInvocation.MyCommand.Name, $privateFunction.Count) Export-ModuleMember -Function $privateFunction.Basename } # Backwards compatibility since we are now trying to use consistent naming Set-Alias -Scope Global -Name 'Connect-AutotaskWebAPI' -Value 'Connect-AtwsWebAPI' # Import service reference and bindings # Load support for TLS 1.2 if the Service Point Manager haven't loaded it yet # This is now a REQUIREMENT to talk to the API endpoints $Protocol = [System.Net.ServicePointManager]::SecurityProtocol if ($Protocol.Tostring() -notlike '*Tls12*') { [System.Net.ServicePointManager]::SecurityProtocol += [System.Net.SecurityProtocolType]::Tls1 2 } # Path to web service reference $code = '{0}\Private\Reference.old.cs' -f $My['ModuleBase'] # List of needed assemblies for Powershell 5.1 $assemblies = @( 'System.ServiceModel' 'System.ServiceModel.Duplex' 'System.ServiceModel.Http' 'System.ServiceModel.NetTcp' 'System.ServiceModel.Security' 'System.Diagnostics.Debug' 'System.Xml' 'System.Xml.ReaderWriter' 'System.Runtime.Serialization' ) # For Powershell versions 6 and higher, add these assemblies if ($PSVersionTable.PSVersion.Major -ge 6) { $assemblies += @( 'netstandard' 'System.Xml.XmlSerializer' 'System.Runtime.Serialization.Xml' 'System.ServiceModel.Primitives' 'System.Private.ServiceModel' 'System.Diagnostics.Tools' ) } # For Powershell versions 7.3.1 and higher, add this assembly $threshold = New-Object System.Version("7.3.0") if ($PSVersionTable.PSVersion -gt $threshold) { $code = '{0}\Private\Reference.cs' -f $My['ModuleBase'] $assemblies += @( 'Microsoft.Bcl.AsyncInterfaces' ) } # Compile webserviceinfo (Reference.cs) and instantiate a SOAP client if ([appdomain]::CurrentDomain.GetAssemblies().exportedtypes.name -notcontains "ATWSSoap") { Add-Type -TypeDefinition (Get-Content -raw $code) -ReferencedAssemblies $assemblies } # Load the cache from disk Initialize-AtwsRamCache # See if they tried to pass any variables if ($Credential) { Write-Verbose ('{0}: Parameters detected. Connecting to Autotask API' -F $MyInvocation.MyCommand.Name) Try { if ($Credential -is [pscredential]) { ## Legacy # The user passed credentials directly $Parameters = @{ Credential = $Credential SecureTrackingIdentifier = ConvertTo-SecureString $ApiTrackingIdentifier -AsPlainText -Force DebugPref = $DebugPreference VerbosePref = $VerbosePreference } $Configuration = New-AtwsModuleConfiguration @Parameters } elseif (Test-AtwsModuleConfiguration -Configuration $Credential) { ## First parameter was a valid configuration object $Configuration = $Credential # Switch to configured debug and verbose preferences $VerbosePreference = $Configuration.VerbosePref $DebugPreference = $Configuration.DebugPref } else { throw (New-Object System.Management.Automation.ParameterBindingException) } ## Connect to the API # or die trying . Connect-AtwsWebServices -Configuration $Configuration -Erroraction Stop } catch { $message = "{0}`n`nStacktrace:`n{1}" -f $_, $_.ScriptStackTrace throw (New-Object System.Configuration.Provider.ProviderException $message) return } # From now on we should have module variable atws available } else { Write-Verbose 'No Credentials were passed with -ArgumentList. Loading module without any connection to Autotask Web Services. Use Connect-AtwsWebAPI to connect.' } # Clean out old cache data # On Windows we store the cache in the WindowsPowerhell folder in My documents # On macOS and Linux we use a dot-folder in the users $HOME folder as is customary # if ([Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([Runtime.InteropServices.OSPlatform]::Windows)) { # $PersonalCacheDir = Join-Path $([environment]::GetFolderPath('MyDocuments')) 'WindowsPowershell\Cache' # } # else { # $PersonalCacheDir = Join-Path $([environment]::GetFolderPath('MyDocuments')) '.config\powershell\atwsCache' # } # 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 } |