Private/Invoke-ServiceNowRestMethod.ps1
<# .SYNOPSIS Retrieves records for the specified table .DESCRIPTION The Get-ServiceNowTable function retrieves records for the specified table .INPUTS None .OUTPUTS System.Management.Automation.PSCustomObject .LINK Service-Now Kingston REST Table API: https://docs.servicenow.com/bundle/kingston-application-development/page/integrate/inbound-rest/concept/c_TableAPI.html Service-Now Table API FAQ: https://hi.service-now.com/kb_view.do?sysparm_article=KB0534905 #> function Invoke-ServiceNowRestMethod { [OutputType([System.Management.Automation.PSCustomObject])] [CmdletBinding(SupportsPaging)] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseBOMForUnicodeEncodedFile', '', Justification = 'issuees with *nix machines and no benefit')] Param ( [parameter()] [ValidateSet('Get', 'Post', 'Patch', 'Delete')] [string] $Method = 'Get', # Name of the table we're querying (e.g. incidents) [parameter(Mandatory, ParameterSetName = 'Table')] [ValidateNotNullOrEmpty()] [Alias('sys_class_name')] [string] $Table, [parameter(ParameterSetName = 'Table')] [ValidateNotNullOrEmpty()] [string] $SysId, [parameter(ParameterSetName = 'Uri')] [ValidateNotNullOrEmpty()] [string] $UriLeaf, # [parameter()] # [hashtable] $Header, [parameter()] [hashtable] $Values, [parameter()] [System.Collections.ArrayList] $Filter, [parameter()] [System.Collections.ArrayList] $Sort = @('opened_at', 'desc'), # sysparm_query param in the format of a ServiceNow encoded query string (see http://wiki.servicenow.com/index.php?title=Encoded_Query_Strings) [Parameter()] [string] $Query, # Fields to return [Parameter()] [Alias('Fields', 'Properties')] [string[]] $Property, # Whether or not to show human readable display values instead of machine values [Parameter()] [ValidateSet('true', 'false', 'all')] [Alias('DisplayValues')] [string] $DisplayValue = 'true', [Parameter()] [hashtable] $Connection, [Parameter()] [hashtable] $ServiceNowSession = $script:ServiceNowSession ) # get header/body auth values $params = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession $params.Method = $Method $params.ContentType = 'application/json' $params.UseBasicParsing = $true if ( $Table ) { # table can either be the actual table name or class name # look up the actual table name $tableName = $script:ServiceNowTable | Where-Object { $_.Name.ToLower() -eq $Table.ToLower() -or $_.ClassName.ToLower() -eq $Table.ToLower() } | Select-Object -ExpandProperty Name # if not in our lookup, just use the table name as provided if ( -not $tableName ) { $tableName = $Table } $params.Uri += "/table/$tableName" if ( $SysId ) { $params.Uri += "/$SysId" } } else { $params.Uri += $UriLeaf } if ( $Method -eq 'Get') { $Body = @{ 'sysparm_display_value' = $DisplayValue 'sysparm_query' = (New-ServiceNowQuery -Filter $Filter -Sort $Sort) 'sysparm_limit' = 10 } # Handle paging parameters # The value of -First defaults to [uint64]::MaxValue if not specified. # If no paging information was provided, default to the legacy behavior, which was to return 10 records. if ($PSCmdlet.PagingParameters.First -ne [uint64]::MaxValue) { $Body['sysparm_limit'] = $PSCmdlet.PagingParameters.First } if ($PSCmdlet.PagingParameters.Skip) { $Body['sysparm_offset'] = $PSCmdlet.PagingParameters.Skip } if ($Query) { $Body.sysparm_query = $Query } if ($Property) { $Body.sysparm_fields = ($Property -join ',').ToLower() } } if ( $Values ) { $Body = $Values | ConvertTo-Json #Convert to UTF8 array to support special chars such as the danish "�","�","�" $body = [System.Text.Encoding]::UTf8.GetBytes($Body) } if ( $Body ) { $params.Body = $Body } Write-Verbose ($params | ConvertTo-Json) # hide invoke-webrequest progress $oldProgressPreference = $ProgressPreference $ProgressPreference = 'SilentlyContinue' try { $response = Invoke-WebRequest @params Write-Debug $response } catch { $ProgressPreference = $oldProgressPreference throw $_ } # validate response switch ($Method) { 'Delete' { if ( $response.StatusCode -ne 204 ) { throw ('"{0} : {1}' -f $response.StatusCode, $response | Out-String ) } } Default { # TODO: this could use some work # checking for content is good, but at times we'll get content that's not valid # eg. html content when a dev instance is hibernating if ( $response.Content ) { $content = $response.content | ConvertFrom-Json if ( $content.PSobject.Properties.Name -contains "result" ) { $records = @($content | Select-Object -ExpandProperty result) } else { $records = @($content) } } else { # invoke-webrequest didn't throw an error per se, but we didn't get content back either throw ('"{0} : {1}' -f $response.StatusCode, $response | Out-String ) } } } $totalRecordCount = 0 if ( $response.Headers.'X-Total-Count' ) { if ($PSVersionTable.PSVersion.Major -lt 6) { $totalRecordCount = [int]$response.Headers.'X-Total-Count' } else { $totalRecordCount = [int]($response.Headers.'X-Total-Count'[0]) } Write-Verbose "Total number of records for this query: $totalRecordCount" } # if option to get all records was provided, loop and get them all if ( $PSCmdlet.PagingParameters.IncludeTotalCount ) { $retrieveRecordCount = $totalRecordCount - $PSCmdlet.PagingParameters.Skip if ( $retrieveRecordCount -ne 0 ) { Write-Warning "Getting $retrieveRecordCount records..." } $setPoint = $params.body.sysparm_offset + $params.body.sysparm_limit while ($totalRecordCount -gt $setPoint) { # up the offset so we get the next set of records $params.body.sysparm_offset += $params.body.sysparm_limit $setPoint = $params.body.sysparm_offset + $params.body.sysparm_limit $end = if ( $totalRecordCount -lt $setPoint ) { $totalRecordCount } else { $setPoint } Write-Verbose ('getting {0}-{1} of {2}' -f ($params.body.sysparm_offset + 1), $end, $totalRecordCount) try { $response = Invoke-WebRequest @params -Verbose:$false } catch { $ProgressPreference = $oldProgressPreference throw $_ } $content = $response.content | ConvertFrom-Json if ( $content.PSobject.Properties.Name -contains "result" ) { $records += $content | Select-Object -ExpandProperty result } else { $records += $content } } if ( $totalRecordCount -ne ($records.count + $PSCmdlet.PagingParameters.Skip) ) { Write-Error ('The expected number of records was not received. This can occur if your -First value, how many records retrieved at once, is too large. Lower this value and try again. Received: {0}, expected: {1}' -f $records.count, ($totalRecordCount - $PSCmdlet.PagingParameters.Skip)) } } # set the progress pref back now that done with invoke-webrequest $ProgressPreference = $oldProgressPreference switch ($Method) { 'Get' { $ConvertToDateField = @('closed_at', 'expected_start', 'follow_up', 'opened_at', 'sys_created_on', 'sys_updated_on', 'work_end', 'work_start') ForEach ($SNResult in $records) { ForEach ($Property in $ConvertToDateField) { If (-not [string]::IsNullOrEmpty($SNResult.$Property)) { Try { # Extract the default Date/Time formatting from the local computer's "Culture" settings, and then create the format to use when parsing the date/time from Service-Now $CultureDateTimeFormat = (Get-Culture).DateTimeFormat $DateFormat = $CultureDateTimeFormat.ShortDatePattern $TimeFormat = $CultureDateTimeFormat.LongTimePattern $DateTimeFormat = "$DateFormat $TimeFormat" $SNResult.$Property = [DateTime]::ParseExact($($SNResult.$Property), $DateTimeFormat, [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None) } Catch { Try { # Universal Format $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss' $SNResult.$Property = [DateTime]::ParseExact($($SNResult.$Property), $DateTimeFormat, [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None) } Catch { # If the local culture and universal formats both fail keep the property as a string (Do nothing) $null = 'Silencing a PSSA alert with this line' } } } } } } { $_ -in 'Post', 'Patch' } { $records = $content | Select-Object -ExpandProperty result } 'Delete' { # nothing to do } Default { # we should never get here given the list of methods is set } } $records } |