Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v2.3
- Add paging support to all `Get-` functions. Use `-First`, `-Skip`, and `-IncludeTotalCount` parameters. In support of this, api calls have been changed from Invoke-RestMethod to Invoke-WebRequest.
- Additional pipline support added for Table and SysId parameters to pipe `Get-` functions to `Update-` and `Remove-`.
- ***Breaking change:*** deprecate `-Limit` parameter. The warning regarding deprecation went out over 2 years ago and now that paging has been implemented, it's a good time for this cleanup. Use `-First` instead.
- 'TableEntry' renamed to 'Record' for `New-`, `Update-`, and `Remove-` functions. Aliases added.

## v2.2
- Add advanced filtering and sorting. Initially implemented with `New-ServiceNowQuery` and `Get-ServiceNowRecord`. Filter with many different comparison operators as well as 'and', 'or', and 'group'ing. Sort ascending or descending against multiple fields. Comparison operators are the same as PowerShell for ease of use. Please use the GitHub Discussions section to provide feedback, thoughts, etc.
- Add `Get-ServiceNowRecord`. This function implements the new advanced filtering and sorting. As long as you know your table name, this can replace all other Get functions.
Expand Down
177 changes: 103 additions & 74 deletions ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
function Invoke-ServiceNowRestMethod {
<#
.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
<#
.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)]
Expand Down Expand Up @@ -51,10 +51,6 @@ function Invoke-ServiceNowRestMethod {
[Parameter()]
[string] $Query,

# Maximum number of records to return
[Parameter()]
[int] $Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down Expand Up @@ -102,64 +98,48 @@ function Invoke-ServiceNowRestMethod {
if ( $SysId ) {
$params.Uri += "/$SysId"
}
} else {
}
else {
$params.Uri += $UriLeaf
}

if ( $Method -eq 'Get') {
$Body = @{
'sysparm_display_value' = $DisplayValues
'sysparm_query' = (New-ServiceNowQuery -Filter $Filter -Sort $Sort)
'sysparm_limit' = 10
}

# Handle paging parameters
# If -Limit was provided, write a warning message, but prioritize it over -First.
# 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 ($PSBoundParameters.ContainsKey('Limit')) {
Write-Warning "The -Limit parameter is deprecated, and may be removed in a future release. Use the -First parameter instead."
$Body['sysparm_limit'] = $Limit
} elseif ($PSCmdlet.PagingParameters.First -ne [uint64]::MaxValue) {
if ($PSCmdlet.PagingParameters.First -ne [uint64]::MaxValue) {
$Body['sysparm_limit'] = $PSCmdlet.PagingParameters.First
} else {
$Body['sysparm_limit'] = 10
}
# else {
# $Body['sysparm_limit'] = 10
# }

if ($PSCmdlet.PagingParameters.Skip) {
$Body['sysparm_offset'] = $PSCmdlet.PagingParameters.Skip
}

if ($PSCmdlet.PagingParameters.IncludeTotalCount) {
# Accuracy is a double between 0.0 and 1.0 representing an estimated percentage accuracy.
# 0.0 means we have no idea and 1.0 means the number is exact.

# ServiceNow does return this information in the X-Total-Count response header,
# but we're currently using Invoke-RestMethod to perform the API call, and Invoke-RestMethod
# does not provide the response headers, so we can't capture this info.

# To properly support this parameter, we'd need to fall back on Invoke-WebRequest, read the
# X-Total-Count header of the response, and update this parameter after performing the API
# call.

# Reference:
# https://developer.servicenow.com/app.do#!/rest_api_doc?v=jakarta&id=r_TableAPI-GET

[double] $accuracy = 0.0
$PSCmdlet.PagingParameters.NewTotalCount($PSCmdlet.PagingParameters.First, $accuracy)
}

# Populate the query
if ($Query) {
$Body.sysparm_query = $Query
} else {
$body.sysparm_query = (New-ServiceNowQuery -Filter $Filter -Sort $Sort)
}

if ($Properties) {
$Body.sysparm_fields = ($Properties -join ',').ToLower()
}
}

# Populate the query
# else {
# $body['sysparm_query'] = (New-ServiceNowQuery -Filter $Filter -Sort $Sort)
# }


if ( $Values ) {
$Body = $Values | ConvertTo-Json

Expand All @@ -173,44 +153,94 @@ function Invoke-ServiceNowRestMethod {

Write-Verbose ($params | ConvertTo-Json)

$response = Invoke-RestMethod @params
# hide invoke-webrequest progress
$oldProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'

$response = Invoke-WebRequest @params

$content = $response.content | ConvertFrom-Json
if ( $content.PSobject.Properties.Name -contains "result" ) {
$records = @($content | Select-Object -ExpandProperty result)
}
else {
$records = @($content)
}

# if option to get all records was provided, loop and get them all
if ( $PSCmdlet.PagingParameters.IncludeTotalCount.IsPresent ) {

$totalRecordCount = [int]$response.Headers.'X-Total-Count'
Write-Verbose "Total number of records found was $totalRecordCount"
Write-Warning "Getting $($totalRecordCount - $PSCmdlet.PagingParameters.Skip) records, this could take a while..."

$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)
$response = Invoke-WebRequest @params -Verbose:$false

$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' {
if ( $response.PSobject.Properties.Name -contains "result" ) {

$result = $response | Select-Object -ExpandProperty result
$ConvertToDateField = @('closed_at', 'expected_start', 'follow_up', 'opened_at', 'sys_created_on', 'sys_updated_on', 'work_end', 'work_start')
ForEach ($SNResult in $Result) {
ForEach ($Property in $ConvertToDateField) {
If (-not [string]::IsNullOrEmpty($SNResult.$Property)) {
$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 {
# 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"
# 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 {
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'
}
}
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'
}
}
}
}
} else {
$response
}
}

{ $_ -in 'Post', 'Patch' } {
$result = $response | Select-Object -ExpandProperty result
$records = $content | Select-Object -ExpandProperty result
}

'Delete' {
Expand All @@ -222,6 +252,5 @@ function Invoke-ServiceNowRestMethod {
}
}

$result
# Invoke-RestMethod -Uri $Uri -Credential $Credential -Body $Body -ContentType "application/json" | Select-Object -ExpandProperty Result
$records
}
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowChangeRequest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ function Get-ServiceNowChangeRequest {
[ValidateSet('Desc', 'Asc')]
[string] $OrderDirection = 'Desc',

# Maximum number of records to return
[Parameter()]
[int] $Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowConfigurationItem.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ function Get-ServiceNowConfigurationItem {
[ValidateSet('Desc', 'Asc')]
[string]$OrderDirection = 'Desc',

# Maximum number of records to return
[Parameter()]
[int]$Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowIncident.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ function Get-ServiceNowIncident {
[ValidateSet('Desc', 'Asc')]
[string]$OrderDirection = 'Desc',

# Maximum number of records to return
[Parameter()]
[int]$Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
6 changes: 5 additions & 1 deletion ServiceNow/Public/Get-ServiceNowRecord.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.DESCRIPTION
Retrieve records from any table with the option to filter, sort, and choose fields.
Given you know the table name, you shouldn't need any other 'Get-' function.
Paging is supported with -First, -Skip, and -IncludeTotalCount.

.PARAMETER Table
Name of the table to be queried, by either table name or class name
Expand Down Expand Up @@ -58,6 +59,10 @@
Get-ServiceNowRecord -Table 'change request' -Filter @('opened_at', '-ge', 'javascript:gs.daysAgoEnd(30)')
Get change requests opened in the last 30 days. Use class name as opposed to table name.

.EXAMPLE
Get-ServiceNowRecord -Table 'change request' -First 100 -IncludeTotalCount
Get all change requests, paging 100 at a time.

.INPUTS
None

Expand All @@ -74,7 +79,6 @@ function Get-ServiceNowRecord {

Param (
[parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[Alias('sys_class_name')]
[string] $Table,

Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowRequest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ function Get-ServiceNowRequest {
[ValidateSet('Desc', 'Asc')]
[string]$OrderDirection = 'Desc',

# Maximum number of records to return
[Parameter()]
[int]$Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowRequestedItem.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ function Get-ServiceNowRequestedItem {
[ValidateSet('Desc', 'Asc')]
[string]$OrderDirection = 'Desc',

# Maximum number of records to return
[parameter()]
[int]$Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
9 changes: 0 additions & 9 deletions ServiceNow/Public/Get-ServiceNowTable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ function Get-ServiceNowTable {
[Parameter()]
[string] $Query,

# Maximum number of records to return
[Parameter()]
[int] $Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down Expand Up @@ -70,11 +66,6 @@ function Get-ServiceNowTable {
ServiceNowSession = $ServiceNowSession
}

# # Only add the Limit parameter if it was explicitly provided
if ($PSBoundParameters.ContainsKey('Limit')) {
$getServiceNowTableSplat.Add('Limit', $Limit)
}

# # Add all provided paging parameters
($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object {
$getServiceNowTableSplat.Add($_, $PSCmdlet.PagingParameters.$_)
Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowTableEntry.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ function Get-ServiceNowTableEntry {
[ValidateSet('Desc', 'Asc')]
[string] $OrderDirection = 'Desc',

# Maximum number of records to return
[parameter()]
[int] $Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowUser.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ function Get-ServiceNowUser{
[ValidateSet('Desc', 'Asc')]
[string]$OrderDirection = 'Desc',

# Maximum number of records to return
[Parameter()]
[int]$Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
4 changes: 0 additions & 4 deletions ServiceNow/Public/Get-ServiceNowUserGroup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ function Get-ServiceNowUserGroup{
[ValidateSet('Desc', 'Asc')]
[string]$OrderDirection = 'Desc',

# Maximum number of records to return
[Parameter()]
[int]$Limit,

# Fields to return
[Parameter()]
[Alias('Fields')]
Expand Down
Loading