lib/Authentication.ps1
## Define a SNSessions Variable to store connection details in New-Variable -Scope global -Name 'SNSessions' -Value @{ } -Force function New-SNSession { <# .SYNOPSIS Creates a connection to a ServiceNow instance .DESCRIPTION This function creates a connection to the specified ServiceNow instance using the provided credentials. This session can be used when calling other functions within the ServiceNow module .PARAMETER SessionName The name that will be used when referring to the created SNSession .PARAMETER Server The URI of the ServiceNow instance to connect to .PARAMETER Credential The credentials to be used twhen connecting to ServiceNow # .PARAMETER SNVersion # Used to override the API version used by other functions in the ServiceNow module .PARAMETER AllowInsecureSSL Boolean indicating whether or not an insecure SSL connection is allowed .PARAMETER Api Boolean indicating wheter or not to also login to the ServiceNow API where possible .PARAMETER Passthru Switch indicating that the newly created SNSession should be output .PARAMETER ProfileName The name of the stored SNProfile which contains the connection settings .EXAMPLE $Session = @{ SessionName = 'Dev07' Server = 'dev68207.service-now.com' Credential = (Get-StoredCredential -Name 'dev68207.service-now.com') } New-SNSession @Session .EXAMPLE Get-SNProfile -Name 'Dev07' | New-SNSession .EXAMPLE New-SNSession -ProfileName 'Dev07' .OUTPUTS None #> [CmdletBinding()] [Alias('Connect-SNServer')] param( [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias('Name')] [String]$SessionName = 'Default', [Parameter(Mandatory = $true, ParameterSetName = 'ByProperty', ValueFromPipelineByPropertyName = $true)] [String]$Server, [Parameter(Mandatory = $true, ParameterSetName = 'ByProperty', ValueFromPipelineByPropertyName = $true)] [PSCredential]$Credential, [Parameter(Mandatory = $false, ParameterSetName = 'ByProperty', ValueFromPipelineByPropertyName = $true)] [Bool]$AllowInsecureSSL = $false, # [Parameter(Mandatory = $false, # ParameterSetName = 'ByProperty', # ValueFromPipelineByPropertyName = $true)] # [Bool]$Api = $true, [Parameter(Mandatory = $false)] [Switch]$Passthru, [Parameter(Mandatory = $true, ParameterSetName = 'ByProfileObject')] [SNProfile]$SNProfile, [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByProfile')] [ArgumentCompleter( { Get-SNProfile -List })] [Alias('Profile')] [String]$ProfileName ) begin { ## Create the SNServers Array that will be reachable at $global:SNSessions if (-not $global:SNSessions) { New-Variable -Name SNSessions -Scope Global -Value @{} } } process { if ($ProfileName) { $SNProfile = Get-SNProfile -Name $ProfileName if (!$SNProfile) { Write-Error "Could not load a ServiceNow profile named '$ProfileName'" return } } if ($SNProfile) { $SessionName = $SNProfile.Name $Server = $SNProfile.Server $Credential = $SNProfile.Credential $AllowInsecureSSL = $SNProfile.AllowInsecureSSL if ([String]::IsNullOrWhiteSpace($Project)) { $Project = $SNProfile.Project } } ## Check for Existing Session to this server if ($global:SNSessions.Keys -contains $SessionName) { $NewSNSession = $global:SNSessions[$SessionName] } else { ## Create a session object for this new connection $NewSNSession = [SNSession]::new($SessionName, $Server, $AllowInsecureSSL) } ## Trim the server name $Instance = $Server -Replace 'https://', '' -Replace 'http://', '' ## Add this shortened Instance name (Just the hostname) into the Session Object $NewSNSession.SNServer = $instance ## ## Create the ServiceNow Headers ## $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $Credential.UserName, $Credential.GetNetworkCredential().Password))) # Set proper Headers $RequestHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $RequestHeaders.Add('Authorization', ('Basic {0}' -f $base64AuthInfo)) $RequestHeaders.Add('Accept', 'application/json') # Format the uri $Uri = "https://$Instance/now/cmdb/meta/cmdb_ci" $RestMethodSplat = @{ Method = 'GET' Uri = $Uri Headers = $RequestHeaders Body = $PostBody SessionVariable = 'SNRestSession' PreserveAuthorizationOnRedirect = $true ContentType = $ContentType SkipCertificateCheck = $AllowInsecureSSL } # Make the request try { Write-Verbose "Web Request Parameters:" Write-Verbose ($WebRequestSplat | ConvertTo-Json -Depth 10) Write-Verbose "Invoking web request" $Response = Invoke-RestMethod @RestMethodSplat Write-Verbose "Response status code: $($Response.StatusCode)" Write-Verbose "Response Content: $($Response.Content)" } catch { throw $_ } ## Look for known conditions if ($Response -match 'Sign in to the site to wake your instance') { throw 'This instance is sleeping, please log into your ServiceNow Developer account to wake the instance.' } ## Create the SNSessionObject that will be checked/used by the rest of the ServiceNow Modules $NewSNSession.SNRestSession = $SNRestSession ## Add this Session to the SNSessions list $global:SNSessions[$SessionName] = $NewSNSession ## Return the session if requested if ($Passthru) { $NewSNSession } } } function Get-SNVersion { <# .SYNOPSIS Gets the version of a given ServiceNow instance .DESCRIPTION This function queries the given ServiceNow server for it's version number and returns it in the specified format .PARAMETER Server The server to be checked .PARAMETER Format The format of the version string that is returned .PARAMETER AllowInsecureSSL Boolean indicating whether or not an insecure SSL connection is allowed .EXAMPLE Get-SNVersion -Server 'tmddev.ServiceNow.net' .OUTPUTS A string containing the server's version in the specified format #> [CmdletBinding()] [OutputType([String])] param( [Parameter(Mandatory = $true)] [String]$Server, [Parameter(Mandatory = $false)] [ValidateSet('VersionSemVer', 'SemVer', 'Minor')] [String]$Format = "SemVer", [Parameter(Mandatory = $false)] [Bool]$AllowInsecureSSL = $false ) ## Select the Version formatter $regex = switch ($Format) { "VersionSemVer" { "/Version\ [0-9].[0-9].[0-9]/" } "SemVer" { "[0-9].[0-9].[0-9]" } "Minor" { "[0-9].[0-9]" } Default { "((.|\n)*)" } } #Honor SSL Settings $SNCertSettings = @{SkipCertificateCheck = $AllowInsecureSSL } $instance = $Server.Replace('/tdstm', '').Replace('https://', '').Replace('http://', '') # Check for a version 4.7, 5.0, 5.1 $Uri = "https://$instance/tdstm/auth/loginInfo" $Response = Invoke-WebRequest -Method Get -Uri $Uri @SNCertSettings if ($Response.StatusCode -eq 200) { $BuildVersion = ($Response.Content | ConvertFrom-Json).data.buildVersion $FinalBuildNumber = Select-String -Pattern $regex -InputObject $BuildVersion if ($FinalBuildNumber.Matches.Count -gt 0) { $FinalBuildNumber.Matches | Select-Object -First 1 | Select-Object -ExpandProperty Value } } else { # Check for 4.6 $Uri = "https://$instance/tdstm/auth/login" try { $Response = Invoke-WebRequest -Method Get -Uri $Uri @SNCertSettings if ($Response.StatusCode -eq 200) { $BuildNumbers = $Response.Content | Select-String -Pattern "Version\ [0-9]\.[0-9]\.[0-9]" if ($BuildNumbers.Matches.Count -gt 0) { $BuildNumbers.Matches | Select-String -Pattern $regex | ForEach-Object { $_.Matches } | Select-Object -First 1 } } } catch { throw "Could not get version" } } } function Get-SNSession { <# .SYNOPSIS Gets a SNSession by Name, Server or Version .PARAMETER Name One or more SNSession names to get .PARAMETER Server One or more SN servers for which a SNSession has been created .PARAMETER Version One or more SN server versions for which a SNSession has been created .EXAMPLE Get-SNSession -Name 'Default', 'SNDDEV' .EXAMPLE Get-SNSession -Version '5.0.*' .EXAMPLE Get-SNSession -Server '*.ServiceNow.net' .OUTPUTS A hashtable with the SNSession details #> [CmdletBinding(DefaultParameterSetName = 'ByName')] param( [Parameter(Mandatory = $false, Position = 0, ParameterSetName = 'ByName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('SessionName')] [String[]]$Name, [Parameter(Mandatory = $false, ParameterSetName = 'ByServer', ValueFromPipelineByPropertyName = $true)] [Alias('SNServer')] [String[]]$Server, [Parameter(Mandatory = $false, ParameterSetName = 'ByVersion', ValueFromPipelineByPropertyName = $true)] [Alias('SNVersion')] [String[]]$Version ) begin { $Keys = @($Global:SNSessions.Keys) } process { switch ($PSCmdlet.ParameterSetName) { 'ByName' { if ($Name) { $Name | ForEach-Object { foreach ($Key in $Keys) { if ($Key -match $_) { $Global:SNSessions[$Key] } } } } else { foreach ($Key in $Keys) { $Global:SNSessions[$Key] } } } 'ByServer' { $Server | ForEach-Object { foreach ($Key in $Keys) { if ($Global:SNSessions[$Key].SNServer -match $_) { $Global:SNSessions[$Key] } } } } 'ByVersion' { $Version | ForEach-Object { foreach ($Key in $Keys) { if ($Global:SNSessions[$Key].SNVersion.Value -match $_) { $Global:SNSessions[$Key] } } } } default { $Global:SNSessions } } } } function Remove-SNSession { <# .SYNOPSIS Disconnects one or more sessions with a ServiceNow instance .DESCRIPTION This function signs out of one or more ServiceNow instances and removes the session from the $Global:SNSessions variable .PARAMETER Name The name of one or more SNSessions to disconnect and remove .PARAMETER InputObject One or more SNSessions to disconnect and remove .EXAMPLE Remove-SNSession -Name 'Default', 'SNDDEV' .EXAMPLE Get-SNSession | Remove-SNSession .OUTPUTS None #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByName')] [Alias('SessionName')] [String[]]$Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByObject')] [Object[]]$InputObject ) process { Write-Verbose "Parameter Set: $($PSCmdlet.ParameterSetName)" # Compile a list of the sessions that need to be disconnected $SessionsToRemove = @() switch ($PSCmdlet.ParameterSetName) { 'ByName' { $Name | ForEach-Object { $SessionsToRemove += $Global:SNSessions[$_] } } 'ByObject' { $SessionsToRemove = $InputObject } } # Iterate over each session that was passed and sign out of SN foreach ($Session in $SessionsToRemove) { Write-Host "Logging out of ServiceNow instance [ " -NoNewline Write-Host $Session.SNServer -ForegroundColor Cyan -NoNewline Write-Host " ]" # Format the parameters for the sign out request $WebRequestSplat = @{ Method = 'POST' Uri = "https://$($Session.SNServer)/tdstm/auth/signOut" WebSession = $Session.SNWebSession ContentType = 'application/json;charset=UTF-8' PreserveAuthorizationOnRedirect = $true SkipCertificateCheck = $Session.AllowInsecureSSL } try { # Make the request to sign out $Response = Invoke-WebRequest @WebRequestSplat if ($Response.StatusCode -eq 200) { # Ensure the sign out was successful $ResponseContent = $Response.Content | ConvertFrom-Json if ($ResponseContent.status -ne 'success') { Write-Error ($ResponseContent.errors -join '; ') } Write-Host "Log out successful!" # Remove the session from the global sessions variable $Global:SNSessions.Remove($Session.Name) } else { Write-Error "Could not sign out of ServiceNow" } } catch { Write-Error $_ } } } } function New-SNProfile { <# .SYNOPSIS Creates a ServiceNow connection profile on the local hard drive .DESCRIPTION This function creates a ServiceNow connection profile on the localk hard drive that can be used with the New-SNSession function to more easily create connections to ServiceNow instances .PARAMETER Name The name name of the profile that will be saved. This name will be used to reference the SNProfile in other functions. .PARAMETER Server The URI of the ServiceNow instance .PARAMETER Project The name of the project on the ServiceNow instance .PARAMETER Credential The credentials used to connect to ServiceNow .PARAMETER AllowInsecureSSL Boolean indicating whether or not an insecure SSL connection is allowed .PARAMETER Passthru Switch indicating that the newly created SNProfile should be output .EXAMPLE New-SNProfile -Name 'SNDDEV-RVTools' -Server 'tmddev.ServiceNow.net' -Project 'RD - RVTools' -Credential (Get-Credential) .EXAMPLE $Profile = @{ Name = 'SNDDEV2' Server = 'tmddev2.ServiceNow.net' Credential = (Get-StoredCredential -Name 'ME') } New-SNProfile @Profile -Passthru | New-SNSession .OUTPUTS If Passthru switch is used, a SNProfile object. Otherwise, none #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String]$Name, [Parameter(Mandatory = $true)] [String]$Server, [Parameter(Mandatory = $false)] [String]$Project, [Parameter(Mandatory = $true)] [PSCredential]$Credential, [Parameter(Mandatory = $false)] [Bool]$AllowInsecureSSL = $false, [Parameter(Mandatory = $false)] [Bool]$Api = $true, [Parameter(Mandatory = $false)] [Switch]$Passthru = $false ) process { $ProfileDirectory = Join-Path $HOME 'TMD_Files' 'Profiles' Test-FolderPath -FolderPath $ProfileDirectory $SNProfile = [SNProfile]::new($Name, $Server, $Project, $Credential, $AllowInsecureSSL, $Api) $SNProfile | Export-Clixml -Path (Join-Path $ProfileDirectory "$Name.tmprofile") if ($Passthru) { $SNProfile } } } function Get-SNProfile { <# .SYNOPSIS Gets a SNProfile that is stored on the local hard drive .DESCRIPTION Gets one or more of the ServiceNow profiles that are saved on the local hard drive .PARAMETER Name The name of the SNProfile to get .PARAMETER List Switch indicating that a list of all SNProfile names should be returned .EXAMPLE $AllProfiles = Get-SNProfile .EXAMPLE Get-SNProfile -Name SNDDEV2 .EXAMPLE Get-SNProfile -List .OUTPUTS One or more objects representing a saved SNProfile #> [CmdletBinding(DefaultParameterSetName = "Single")] [OutputType([SNProfile])] param( [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Single")] [ArgumentCompleter( { Get-SNProfile -List })] [String]$Name, [Parameter(Mandatory = $true, ParameterSetName = "List")] [Switch]$List ) process { try { if (-not $Name -or $List) { $ProfileDirectory = Join-Path $HOME 'TMD_Files' 'Profiles' $ProfileFiles = Get-ChildItem -Path $ProfileDirectory -Filter '*.tmprofile' -File $ProfileFiles | ForEach-Object { if ($List) { $_.Name -replace $_.Extension, '' } else { $SNProfile = Import-Clixml -Path $_.FullName [SNProfile]::new( $SNProfile.Name, $SNProfile.Server, $SNProfile.Project, $SNProfile.Credential, $SNProfile.AllowInsecureSSL, $SNProfile.Api ) } } } else { $ProfileFilePath = Join-Path $HOME 'TMD_Files' 'Profiles' ($Name + '.tmprofile') if (Test-Path -Path $ProfileFilePath) { $SNProfile = Import-Clixml -Path $ProfileFilePath [SNProfile]::new( $SNProfile.Name, $SNProfile.Server, $SNProfile.Project, $SNProfile.Credential, $SNProfile.AllowInsecureSSL, $SNProfile.Api ) } } } catch { $PSCmdlet.WriteError($_) } } } function Remove-SNProfile { <# .SYNOPSIS Removes a previously stored SNProfile from the local hard drive .DESCRIPTION Overwrites a stored .tmprofile file multiple times with a random bytestream before deleting it from the local hard drive .PARAMETER Name The name of the SNProfile to be removed .PARAMETER OverwriteCount The number of times to overwrite the file before removing it .EXAMPLE Remove-SNProfile -Name 'SNDDEV2' .EXAMPLE Get-SNProfile | Remove-SNProfile .NOTES General notes #> [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ArgumentCompleter( { Get-SNProfile -List })] [String]$Name, [Parameter(Mandatory = $false)] [Int]$OverwriteCount = 500 ) process { $ProfileFilePath = Join-Path $HOME 'TMD_Files' 'Profiles' ($Name + '.tmprofile') Write-Verbose "Processing file '$ProfileFilePath'" if (Test-Path -PathType Leaf -Path $ProfileFilePath) { $BufferSize = $Name.Length $RandomDataBuffer = [System.Byte[]]::new($BufferSize) Write-Verbose "Overwriting the data within the file $OverwriteCount time(s)" for ($i = 0; $i -lt $OverwriteCount; $i++) { $Random = [System.Random]::new() $Random.NextBytes($RandomDataBuffer) | Set-Content -Path $ProfileFilePath -AsByteStream } Write-Verbose "Removing the file" Remove-Item -Path $ProfileFilePath -Force } else { $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [Exception]::new("A ServiceNow profile with the name '$Name' does not exist."), "SND.AUTH02", [System.Management.Automation.ErrorCategory]::InvalidArgument, $ProfileFilePath ) $PSCmdlet.WriteError($ErrorRecord) } } } |