From fe9b6b879093feedbd1a360f8258573924abfa9f Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:53:15 -0500 Subject: [PATCH 01/12] Update Get-ServiceNowRecord.ps1 Fixes error "Id must either be a SysId 32 character alphanumeric or Number with prefix and id." and retains ID from reference table for further lookup. --- ServiceNow/Public/Get-ServiceNowRecord.ps1 | 33 ++++++++++------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/ServiceNow/Public/Get-ServiceNowRecord.ps1 b/ServiceNow/Public/Get-ServiceNowRecord.ps1 index 5441853..7e65143 100644 --- a/ServiceNow/Public/Get-ServiceNowRecord.ps1 +++ b/ServiceNow/Public/Get-ServiceNowRecord.ps1 @@ -173,8 +173,7 @@ function Get-ServiceNowRecord { [ValidateScript( { if ($_ -match '^[a-zA-Z0-9]{32}$' -or $_ -match '^([a-zA-Z]+)[0-9]+$') { $true - } - else { + } else { throw 'Id must either be a SysId 32 character alphanumeric or Number with prefix and id.' } })] @@ -185,8 +184,7 @@ function Get-ServiceNowRecord { [ValidateScript( { if ($_ -match '^[a-zA-Z0-9]{32}$' -or $_ -match '^([a-zA-Z]+)[0-9]+$') { $true - } - else { + } else { throw 'ParentId must either be a SysId 32 character alphanumeric or Number with prefix and id.' } })] @@ -269,8 +267,7 @@ function Get-ServiceNowRecord { if ( $thisID -match '^[a-zA-Z0-9]{32}$' ) { $thisParams.Filter += , @('sys_id', '-eq', $thisID) - } - else { + } else { $thisParams.Filter += , @('number', '-eq', $thisID) } } @@ -283,8 +280,7 @@ function Get-ServiceNowRecord { if ( $ParentID -match '^[a-zA-Z0-9]{32}$' ) { $thisParams.Filter += , @('parent.sys_id', '-eq', $ParentID) - } - else { + } else { $thisParams.Filter += , @('parent.number', '-eq', $ParentID) } @@ -363,7 +359,13 @@ function Get-ServiceNowRecord { # show the underlying value if the option is a reference type if ( $newVar.Type -eq 'Reference' ) { + #do not do any further lookup when the value is blank or null + #resolves #234 and 262 + if ($var.'sc_item_option.value' -eq "" -or $null -eq $var.'sc_item_option.value') { + continue + } $newVar | Add-Member @{'ReferenceTable' = $var.'sc_item_option.item_option_new.reference' } + $newVar | Add-Member @{'ReferenceID' = $var.'sc_item_option.value' } # issue 234. ID might not be sysid or number for reference...odd $refValue = Get-ServiceNowRecord -Table $var.'sc_item_option.item_option_new.reference' -ID $var.'sc_item_option.value' -Property name -AsValue -ServiceNowSession $ServiceNowSession -ErrorAction SilentlyContinue if ( $refValue ) { @@ -373,33 +375,28 @@ function Get-ServiceNowRecord { if ( $var.'sc_item_option.item_option_new.name' ) { $record.CustomVariable | Add-Member @{ $var.'sc_item_option.item_option_new.name' = $newVar } - } - else { + } else { $record.CustomVariable | Add-Member @{ $var.'sc_item_option.item_option_new.question_text' = $newVar } } } if ( $addedSysIdProp ) { $record | Select-Object -Property * -ExcludeProperty sys_id - } - else { + } else { $record } } - } - else { + } else { # format the results if ( $Property ) { if ( $Property.Count -eq 1 -and $AsValue ) { $propName = $result | Get-Member | Where-Object { $_.MemberType -eq 'NoteProperty' } | Select-Object -ExpandProperty Name $result.$propName - } - else { + } else { $result } - } - else { + } else { if ($thisTable.Type) { $result | ForEach-Object { $_.PSObject.TypeNames.Insert(0, $thisTable.Type) } } From ba19c85b8309f6f5c5a4e0261b13e128d423b2a8 Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:20:02 -0500 Subject: [PATCH 02/12] Update Get-ServiceNowRecord.ps1 Additional fixes for reference table lookup where value is not in the 32 character format expected by SNOW. --- ServiceNow/Public/Get-ServiceNowRecord.ps1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ServiceNow/Public/Get-ServiceNowRecord.ps1 b/ServiceNow/Public/Get-ServiceNowRecord.ps1 index 7e65143..9966d26 100644 --- a/ServiceNow/Public/Get-ServiceNowRecord.ps1 +++ b/ServiceNow/Public/Get-ServiceNowRecord.ps1 @@ -364,10 +364,15 @@ function Get-ServiceNowRecord { if ($var.'sc_item_option.value' -eq "" -or $null -eq $var.'sc_item_option.value') { continue } - $newVar | Add-Member @{'ReferenceTable' = $var.'sc_item_option.item_option_new.reference' } - $newVar | Add-Member @{'ReferenceID' = $var.'sc_item_option.value' } - # issue 234. ID might not be sysid or number for reference...odd - $refValue = Get-ServiceNowRecord -Table $var.'sc_item_option.item_option_new.reference' -ID $var.'sc_item_option.value' -Property name -AsValue -ServiceNowSession $ServiceNowSession -ErrorAction SilentlyContinue + $sysidPattern = "[0-9a-fA-F]{32}" + $sysid = [Regex]::Matches($var.'sc_item_option.value', $sysidPattern).Value + if ($sysid) { + Write-Verbose "Custom variable lookup for $($newvar.name) from table '$($var.'sc_item_option.item_option_new.reference')' sysid:'$($var.'sc_item_option.value')'" + $newVar | Add-Member @{'ReferenceTable' = $var.'sc_item_option.item_option_new.reference' } + $newVar | Add-Member @{'ReferenceID' = $var.'sc_item_option.value' } + # issue 234. ID might not be sysid or number for reference...odd + $refValue = Get-ServiceNowRecord -Table $var.'sc_item_option.item_option_new.reference' -ID $var.'sc_item_option.value' -Property name -AsValue -ServiceNowSession $ServiceNowSession -ErrorAction SilentlyContinue + } if ( $refValue ) { $newVar.Value = $refValue } From 8869f17bb60ce31a884982d8f5e40bc8c06ad244 Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Fri, 12 Jul 2024 08:42:58 -0500 Subject: [PATCH 03/12] Update ServiceNow/Public/Get-ServiceNowRecord.ps1 Co-authored-by: Greg Brownstein --- ServiceNow/Public/Get-ServiceNowRecord.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ServiceNow/Public/Get-ServiceNowRecord.ps1 b/ServiceNow/Public/Get-ServiceNowRecord.ps1 index 9966d26..df04349 100644 --- a/ServiceNow/Public/Get-ServiceNowRecord.ps1 +++ b/ServiceNow/Public/Get-ServiceNowRecord.ps1 @@ -372,10 +372,11 @@ function Get-ServiceNowRecord { $newVar | Add-Member @{'ReferenceID' = $var.'sc_item_option.value' } # issue 234. ID might not be sysid or number for reference...odd $refValue = Get-ServiceNowRecord -Table $var.'sc_item_option.item_option_new.reference' -ID $var.'sc_item_option.value' -Property name -AsValue -ServiceNowSession $ServiceNowSession -ErrorAction SilentlyContinue - } - if ( $refValue ) { - $newVar.Value = $refValue - } + if ( $refValue ) { + $newVar.Value = $refValue + } + } + } if ( $var.'sc_item_option.item_option_new.name' ) { From ec233edcada17f6dc4e152b276880b08e283a36e Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:31:10 -0500 Subject: [PATCH 04/12] Create New-ServiceNowCatalogItem.ps1 --- .../Public/New-ServiceNowCatalogItem.ps1 | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 ServiceNow/Public/New-ServiceNowCatalogItem.ps1 diff --git a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 new file mode 100644 index 0000000..b29209a --- /dev/null +++ b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 @@ -0,0 +1,97 @@ +<# +.SYNOPSIS + Submit a catalog request using Service Catalog API + +.DESCRIPTION + Create a new catalog item request using Service Catalog API. Reference: https://www.servicenow.com/community/itsm-articles/submit-catalog-request-using-service-catalog-api/ta-p/2305836 + +.PARAMETER CatalogItemName + Name of the catalog item that will be created + +.PARAMETER CatalogItemID + SysID of the catalog item that will be created + +.PARAMETER Variables + Key/value pairs of variable names and their values + +.PARAMETER PassThru + If provided, the new record will be returned + +.PARAMETER Connection + Azure Automation Connection object containing username, password, and URL for the ServiceNow instance + +.PARAMETER ServiceNowSession + ServiceNow session created by New-ServiceNowSession. Will default to script-level variable $ServiceNowSession. + +.EXAMPLE + New-ServiceNowRecord -CatalogItemName "Standard Laptop" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } + + Raise a new catalog request using Item Name + +.EXAMPLE + New-ServiceNowRecord -CatalogItemID "04b7e94b4f7b42000086eeed18110c7fd" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } + + Raise a new catalog request using Item ID + +.INPUTS + InputData + +.OUTPUTS + PSCustomObject if PassThru provided +#> +function New-ServiceNowCatalogItem { + [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ID')] + param + ( + [Parameter(Mandatory, ParameterSetName = 'Name')] + [string]$CatalogItemName, + [Parameter(Mandatory, ParameterSetName = 'ID')] + [string]$CatalogItemID, + [Parameter(Mandatory, ParameterSetName = 'Name')] + [Parameter(Mandatory, ParameterSetName = 'ID')] + [Alias('Variables')] + [hashtable]$InputData, + [Parameter()][Hashtable]$Connection, + [Parameter()][hashtable]$ServiceNowSession = $script:ServiceNowSession, + [Parameter()][switch]$PassThru + ) + + begin { + if ($CatalogItemName) { + #Lookup the sys_id of the Catalog Item name + $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItemName )).sys_id + if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by name '$($catalogitemname)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItemName)'" } + } + } + process { + + $AddItemToCart = @{ + Method = 'Post' + UriLeaf = "/servicecatalog/items/{0}/add_to_cart" -f $CatalogItemID + Values = @{'sysparm_quantity' = 1; 'variables' = $InputData } + Namespace = 'sn_sc' + Connection = $Connection + ServiceNowSession = $ServiceNowSession + } + + if ( $PSCmdlet.ShouldProcess($CatalogItemID, 'Create new catalog item request') ) { + + $AddItemCartResponse = Invoke-ServiceNowRestMethod @AddItemToCart + + if ($AddItemCartResponse.cart_id) { + $SubmitOrder = @{ + Method = 'Post' + UriLeaf = "/servicecatalog/cart/submit_order" + Namespace = 'sn_sc' + Connection = $Connection + ServiceNowSession = $ServiceNowSession + } + + $SubmitOrderResponse = Invoke-ServiceNowRestMethod @SubmitOrder + } + if ( $PassThru ) { + $SubmitOrderResponse + } + } + } +} From 1f617796f714e4d0b72d45dd83f2e9ab3a0bd21d Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:32:06 -0500 Subject: [PATCH 05/12] Update Invoke-ServiceNowRestMethod.ps1 --- .../Private/Invoke-ServiceNowRestMethod.ps1 | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1 b/ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1 index 9345968..0b5d897 100644 --- a/ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1 +++ b/ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1 @@ -17,7 +17,7 @@ function Invoke-ServiceNowRestMethod { [CmdletBinding(SupportsPaging)] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseBOMForUnicodeEncodedFile', '', Justification = 'issuees with *nix machines and no benefit')] - Param ( + param ( [parameter()] [ValidateSet('Get', 'Post', 'Patch', 'Delete')] [string] $Method = 'Get', @@ -48,6 +48,9 @@ function Invoke-ServiceNowRestMethod { [parameter()] [string] $FilterString, + [parameter()] + [string] $Namespace, + [parameter()] [object[]] $Sort = @('opened_at', 'desc'), @@ -74,7 +77,11 @@ function Invoke-ServiceNowRestMethod { ) # get header/body auth values - $params = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession + if ($namespace) { + $params = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession -N $namespace + } else { + $params = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession + } $params.Method = $Method $params.ContentType = 'application/json' @@ -93,8 +100,7 @@ function Invoke-ServiceNowRestMethod { if ( $SysId ) { $params.Uri += "/$SysId" } - } - else { + } else { $params.Uri += $UriLeaf } @@ -153,8 +159,7 @@ function Invoke-ServiceNowRestMethod { try { $response = Invoke-WebRequest @params Write-Debug $response - } - catch { + } catch { $ProgressPreference = $oldProgressPreference throw $_ } @@ -174,12 +179,10 @@ function Invoke-ServiceNowRestMethod { $content = $response.content | ConvertFrom-Json if ( $content.PSobject.Properties.Name -contains "result" ) { $records = @($content | Select-Object -ExpandProperty result) - } - else { + } else { $records = @($content) } - } - else { + } 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 ) } @@ -190,8 +193,7 @@ function Invoke-ServiceNowRestMethod { if ( $response.Headers.'X-Total-Count' ) { if ($PSVersionTable.PSVersion.Major -lt 6) { $totalRecordCount = [int]$response.Headers.'X-Total-Count' - } - else { + } else { $totalRecordCount = [int]($response.Headers.'X-Total-Count'[0]) } Write-Verbose "Total number of records for this query: $totalRecordCount" @@ -215,16 +217,14 @@ function Invoke-ServiceNowRestMethod { $end = if ( $totalRecordCount -lt $setPoint ) { $totalRecordCount - } - else { + } 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 { + } catch { $ProgressPreference = $oldProgressPreference throw $_ } @@ -232,8 +232,7 @@ function Invoke-ServiceNowRestMethod { $content = $response.content | ConvertFrom-Json if ( $content.PSobject.Properties.Name -contains "result" ) { $records += $content | Select-Object -ExpandProperty result - } - else { + } else { $records += $content } } @@ -249,18 +248,17 @@ function Invoke-ServiceNowRestMethod { 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 { + 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 = [string[]]@("$DateFormat $TimeFormat", 'yyyy-MM-dd HH:mm:ss') $SNResult.$Property = [DateTime]::ParseExact($($SNResult.$Property), $DateTimeFormat, [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None) - } - Catch { + } 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' } @@ -283,4 +281,4 @@ function Invoke-ServiceNowRestMethod { } $records -} \ No newline at end of file +} From 92530f7cadea17bdd27d172bef19b04d8031909b Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:34:36 -0500 Subject: [PATCH 06/12] Update Get-ServiceNowAuth.ps1 Add Namespace parameter to support different API endpoints; defaults to 'now' --- ServiceNow/Private/Get-ServiceNowAuth.ps1 | 39 ++++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/ServiceNow/Private/Get-ServiceNowAuth.ps1 b/ServiceNow/Private/Get-ServiceNowAuth.ps1 index 9f221a5..e2a3ebd 100644 --- a/ServiceNow/Private/Get-ServiceNowAuth.ps1 +++ b/ServiceNow/Private/Get-ServiceNowAuth.ps1 @@ -13,11 +13,15 @@ function Get-ServiceNowAuth { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'requirement of azure automation')] - Param ( + param ( [Parameter()] [Alias('C')] [hashtable] $Connection, + [Parameter()] + [Alias('N')] + [string] $Namespace = 'now', + [Parameter()] [Alias('S')] [hashtable] $ServiceNowSession @@ -30,7 +34,11 @@ function Get-ServiceNowAuth { process { if ( $ServiceNowSession.Count -gt 0 ) { - $hashOut.Uri = $ServiceNowSession.BaseUri + if ($Namespace -ne 'now') { + $hashOut.Uri = $($ServiceNowSession.BaseUri -split ('api'))[0] + 'api/' + $Namespace + } else { + $hashOut.Uri = $ServiceNowSession.BaseUri + } # check if we need a new access token if ( $ServiceNowSession.ExpiresOn -lt (Get-Date) -and $ServiceNowSession.RefreshToken -and $ServiceNowSession.ClientCredential ) { @@ -46,7 +54,7 @@ function Get-ServiceNowAuth { refresh_token = $ServiceNowSession.RefreshToken.GetNetworkCredential().password } } - + $response = Invoke-RestMethod @refreshParams $ServiceNowSession.AccessToken = New-Object System.Management.Automation.PSCredential('AccessToken', ($response.access_token | ConvertTo-SecureString -AsPlainText -Force)) @@ -64,8 +72,7 @@ function Get-ServiceNowAuth { $hashOut.Headers = @{ 'Authorization' = 'Bearer {0}' -f $ServiceNowSession.AccessToken.GetNetworkCredential().password } - } - else { + } else { # issue 248 $pair = '{0}:{1}' -f $ServiceNowSession.Credential.UserName, $ServiceNowSession.Credential.GetNetworkCredential().Password $hashOut.Headers = @{ Authorization = 'Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) } @@ -75,34 +82,28 @@ function Get-ServiceNowAuth { $hashOut.Proxy = $ServiceNowSession.Proxy if ( $ServiceNowSession.ProxyCredential ) { $hashOut.ProxyCredential = $ServiceNowSession.ProxyCredential - } - else { + } else { $hashOut.ProxyUseDefaultCredentials = $true } } - } - elseif ( $Connection ) { + } elseif ( $Connection ) { Write-Verbose 'connection' # issue 248 $pair = '{0}:{1}' -f $Connection.Username, $Connection.Password $hashOut.Headers = @{ Authorization = 'Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) } - $hashOut.Uri = 'https://{0}/api/now/v1' -f $Connection.ServiceNowUri - } - elseif ( $env:SNOW_SERVER ) { - $hashOut.Uri = 'https://{0}/api/now' -f $env:SNOW_SERVER + $hashOut.Uri = 'https://{0}/api/{1}/v1' -f $Connection.ServiceNowUri, $Namespace + } elseif ( $env:SNOW_SERVER ) { + $hashOut.Uri = 'https://{0}/api/{1}' -f $env:SNOW_SERVER, $Namespace if ( $env:SNOW_TOKEN ) { $hashOut.Headers = @{ 'Authorization' = 'Bearer {0}' -f $env:SNOW_TOKEN } - } - elseif ( $env:SNOW_USER -and $env:SNOW_PASS ) { + } elseif ( $env:SNOW_USER -and $env:SNOW_PASS ) { $hashOut.Credential = New-Object System.Management.Automation.PSCredential($env:SNOW_USER, ($env:SNOW_PASS | ConvertTo-SecureString -AsPlainText -Force)) - } - else { + } else { throw 'A ServiceNow server environment variable has been set, but authentication via SNOW_TOKEN or SNOW_USER/SNOW_PASS was not found' } - } - else { + } else { throw "You must authenticate by either calling the New-ServiceNowSession cmdlet or passing in an Azure Automation connection object" } } From 429568924c2f8b61174676ea101d74eb34c0a57d Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:37:28 -0500 Subject: [PATCH 07/12] Update ServiceNow.psd1 Versioned PSD with Minor increase for new functionality and backwards-compatible changes. --- ServiceNow/ServiceNow.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ServiceNow/ServiceNow.psd1 b/ServiceNow/ServiceNow.psd1 index d23710c..91bbb9f 100644 --- a/ServiceNow/ServiceNow.psd1 +++ b/ServiceNow/ServiceNow.psd1 @@ -12,7 +12,7 @@ RootModule = 'ServiceNow.psm1' # Version number of this module. -ModuleVersion = '4.1.0' +ModuleVersion = '4.2.0' # Supported PSEditions # CompatiblePSEditions = @() From 38dc59236032d43053fea79216d6ab9ecd244284 Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:14:59 -0500 Subject: [PATCH 08/12] Update ServiceNow.psd1 Add New-ServiceNowCatalog declaration to FunctionsToExport. --- ServiceNow/ServiceNow.psd1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ServiceNow/ServiceNow.psd1 b/ServiceNow/ServiceNow.psd1 index 91bbb9f..9c137e1 100644 --- a/ServiceNow/ServiceNow.psd1 +++ b/ServiceNow/ServiceNow.psd1 @@ -76,7 +76,8 @@ FunctionsToExport = 'New-ServiceNowConfigurationItem', 'Get-ServiceNowRecord', 'New-ServiceNowQuery', 'New-ServiceNowRecord', 'Remove-ServiceNowAttachment', 'Remove-ServiceNowRecord', 'Update-ServiceNowRecord', 'Export-ServiceNowRecord', - 'Invoke-ServiceNowGraphQL', 'New-ServiceNowChangeTask' + 'Invoke-ServiceNowGraphQL', 'New-ServiceNowChangeTask', + 'New-ServiceNowCatalogItem' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() From 4f6bf2b42a3cbe28b0e851cbc4c7be3673e3f77b Mon Sep 17 00:00:00 2001 From: CATgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:29:45 -0600 Subject: [PATCH 09/12] Revert ServiceNow.psd1 --- ServiceNow/ServiceNow.psd1 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ServiceNow/ServiceNow.psd1 b/ServiceNow/ServiceNow.psd1 index 9c137e1..afd8a89 100644 --- a/ServiceNow/ServiceNow.psd1 +++ b/ServiceNow/ServiceNow.psd1 @@ -12,7 +12,7 @@ RootModule = 'ServiceNow.psm1' # Version number of this module. -ModuleVersion = '4.2.0' +ModuleVersion = '4.1.0' # Supported PSEditions # CompatiblePSEditions = @() @@ -76,8 +76,7 @@ FunctionsToExport = 'New-ServiceNowConfigurationItem', 'Get-ServiceNowRecord', 'New-ServiceNowQuery', 'New-ServiceNowRecord', 'Remove-ServiceNowAttachment', 'Remove-ServiceNowRecord', 'Update-ServiceNowRecord', 'Export-ServiceNowRecord', - 'Invoke-ServiceNowGraphQL', 'New-ServiceNowChangeTask', - 'New-ServiceNowCatalogItem' + 'Invoke-ServiceNowGraphQL', 'New-ServiceNowChangeTask' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() @@ -137,4 +136,3 @@ PrivateData = @{ # DefaultCommandPrefix = '' } - From 426dd6771816051effc02db641c4428a78259537 Mon Sep 17 00:00:00 2001 From: catgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:32:08 -0600 Subject: [PATCH 10/12] Adjust New-ServiceNowCatalogItem to reduce parameter complexity by supporting ID or name lookup for CatalogItem. --- .../Public/New-ServiceNowCatalogItem.ps1 | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 index b29209a..2ab3855 100644 --- a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 +++ b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 @@ -5,11 +5,8 @@ .DESCRIPTION Create a new catalog item request using Service Catalog API. Reference: https://www.servicenow.com/community/itsm-articles/submit-catalog-request-using-service-catalog-api/ta-p/2305836 -.PARAMETER CatalogItemName - Name of the catalog item that will be created - -.PARAMETER CatalogItemID - SysID of the catalog item that will be created +.PARAMETER CatalogItem + Name or ID of the catalog item that will be created .PARAMETER Variables Key/value pairs of variable names and their values @@ -24,12 +21,12 @@ ServiceNow session created by New-ServiceNowSession. Will default to script-level variable $ServiceNowSession. .EXAMPLE - New-ServiceNowRecord -CatalogItemName "Standard Laptop" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } + New-ServiceNowCatalogItem -CatalogItem "Standard Laptop" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } Raise a new catalog request using Item Name .EXAMPLE - New-ServiceNowRecord -CatalogItemID "04b7e94b4f7b42000086eeed18110c7fd" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } + New-ServiceNowCatalogItem -CatalogItem "04b7e94b4f7b42000086eeed18110c7fd" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } Raise a new catalog request using Item ID @@ -40,15 +37,12 @@ PSCustomObject if PassThru provided #> function New-ServiceNowCatalogItem { - [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ID')] + [CmdletBinding(SupportsShouldProcess)] param ( - [Parameter(Mandatory, ParameterSetName = 'Name')] - [string]$CatalogItemName, - [Parameter(Mandatory, ParameterSetName = 'ID')] - [string]$CatalogItemID, - [Parameter(Mandatory, ParameterSetName = 'Name')] - [Parameter(Mandatory, ParameterSetName = 'ID')] + [Parameter(Mandatory)] + [string]$CatalogItem, + [Parameter(Mandatory)] [Alias('Variables')] [hashtable]$InputData, [Parameter()][Hashtable]$Connection, @@ -57,10 +51,13 @@ function New-ServiceNowCatalogItem { ) begin { - if ($CatalogItemName) { - #Lookup the sys_id of the Catalog Item name - $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItemName )).sys_id - if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by name '$($catalogitemname)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItemName)'" } + if ($CatalogItem -match '^[a-zA-Z0-9]{32}$') { + #Verify the sys_id of the Catalog Item + $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -ID $CatalogItem).sys_id + } else { + #Lookup the sys_id of the Catalog Item + $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItem )).sys_id + if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by name '$($CatalogItem)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItem)'" } } } process { From 3ca6dbcc0d8cb2812729cd9032052f58865e6dba Mon Sep 17 00:00:00 2001 From: catgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:32:08 -0600 Subject: [PATCH 11/12] Adjust New-ServiceNowCatalogItem to reduce parameter complexity by supporting ID or name lookup for CatalogItem. --- .../Public/New-ServiceNowCatalogItem.ps1 | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 index b29209a..92c185e 100644 --- a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 +++ b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 @@ -5,11 +5,8 @@ .DESCRIPTION Create a new catalog item request using Service Catalog API. Reference: https://www.servicenow.com/community/itsm-articles/submit-catalog-request-using-service-catalog-api/ta-p/2305836 -.PARAMETER CatalogItemName - Name of the catalog item that will be created - -.PARAMETER CatalogItemID - SysID of the catalog item that will be created +.PARAMETER CatalogItem + Name or ID of the catalog item that will be created .PARAMETER Variables Key/value pairs of variable names and their values @@ -24,12 +21,12 @@ ServiceNow session created by New-ServiceNowSession. Will default to script-level variable $ServiceNowSession. .EXAMPLE - New-ServiceNowRecord -CatalogItemName "Standard Laptop" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } + New-ServiceNowCatalogItem -CatalogItem "Standard Laptop" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } Raise a new catalog request using Item Name .EXAMPLE - New-ServiceNowRecord -CatalogItemID "04b7e94b4f7b42000086eeed18110c7fd" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } + New-ServiceNowCatalogItem -CatalogItem "04b7e94b4f7b42000086eeed18110c7fd" -Variables @{'acrobat' = 'true'; 'photoshop' = 'true'; ' Additional_software_requirements' = 'Testing Service catalog API' } Raise a new catalog request using Item ID @@ -40,15 +37,12 @@ PSCustomObject if PassThru provided #> function New-ServiceNowCatalogItem { - [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ID')] + [CmdletBinding(SupportsShouldProcess)] param ( - [Parameter(Mandatory, ParameterSetName = 'Name')] - [string]$CatalogItemName, - [Parameter(Mandatory, ParameterSetName = 'ID')] - [string]$CatalogItemID, - [Parameter(Mandatory, ParameterSetName = 'Name')] - [Parameter(Mandatory, ParameterSetName = 'ID')] + [Parameter(Mandatory)] + [string]$CatalogItem, + [Parameter(Mandatory)] [Alias('Variables')] [hashtable]$InputData, [Parameter()][Hashtable]$Connection, @@ -57,10 +51,14 @@ function New-ServiceNowCatalogItem { ) begin { - if ($CatalogItemName) { - #Lookup the sys_id of the Catalog Item name - $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItemName )).sys_id - if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by name '$($catalogitemname)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItemName)'" } + if ($CatalogItem -match '^[a-zA-Z0-9]{32}$') { + #Verify the sys_id of the Catalog Item + $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -ID $CatalogItem).sys_id + if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by ID '$($CatalogItem)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItem)'" } + } else { + #Lookup the sys_id of the Catalog Item + $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItem )).sys_id + if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by name '$($CatalogItem)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItem)'" } } } process { From fe372235fb7f22c159ca5d79a700f83787e718b1 Mon Sep 17 00:00:00 2001 From: catgwalker <78571293+CATgwalker@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:52:43 -0600 Subject: [PATCH 12/12] Added delayed checkout functionality, pipeline support of returned request response, --- .../Public/New-ServiceNowCatalogItem.ps1 | 27 +++++--- .../Public/Submit-ServiceNowCatalogOrder.ps1 | 63 +++++++++++++++++++ 2 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 ServiceNow/Public/Submit-ServiceNowCatalogOrder.ps1 diff --git a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 index ce897b8..94c90c8 100644 --- a/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 +++ b/ServiceNow/Public/New-ServiceNowCatalogItem.ps1 @@ -11,6 +11,9 @@ .PARAMETER Variables Key/value pairs of variable names and their values +.PARAMETER CheckoutImmediately + If provided, a second Post for cart checkout to submit_order API Endpoint will be sent + .PARAMETER PassThru If provided, the new record will be returned @@ -47,20 +50,21 @@ function New-ServiceNowCatalogItem { [hashtable]$InputData, [Parameter()][Hashtable]$Connection, [Parameter()][hashtable]$ServiceNowSession = $script:ServiceNowSession, + [Parameter()][switch]$CheckoutImmediately, [Parameter()][switch]$PassThru ) begin { + if (-not $PSBoundParameters.ContainsKey('CheckoutImmediately')) { + $CheckoutImmediately = $false + } if ($CatalogItem -match '^[a-zA-Z0-9]{32}$') { #Verify the sys_id of the Catalog Item - $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -ID $CatalogItem).sys_id -<<<<<<< HEAD + $CatalogItemID = Get-ServiceNowRecord -Table sc_cat_item -AsValue -ID $CatalogItem -Property sys_id if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by ID '$($CatalogItem)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItem)'" } -======= ->>>>>>> 426dd6771816051effc02db641c4428a78259537 } else { #Lookup the sys_id of the Catalog Item - $CatalogItemID = (Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItem )).sys_id + $CatalogItemID = Get-ServiceNowRecord -Table sc_cat_item -AsValue -Filter @('name', '-eq', $CatalogItem ) -Property sys_id if ([string]::IsNullOrEmpty($CatalogItemID)) { throw "Unable to find catalog item by name '$($CatalogItem)'" } else { Write-Verbose "Found $($catalogitemid) via lookup from '$($CatalogItem)'" } } } @@ -79,7 +83,7 @@ function New-ServiceNowCatalogItem { $AddItemCartResponse = Invoke-ServiceNowRestMethod @AddItemToCart - if ($AddItemCartResponse.cart_id) { + if ($AddItemCartResponse.cart_id -and $CheckoutImmediately) { $SubmitOrder = @{ Method = 'Post' UriLeaf = "/servicecatalog/cart/submit_order" @@ -89,10 +93,13 @@ function New-ServiceNowCatalogItem { } $SubmitOrderResponse = Invoke-ServiceNowRestMethod @SubmitOrder + + if ($PassThru) { + $SubmitOrderResponse | Select-Object @{'n' = 'number'; 'e' = { $_.request_number } }, request_id + } } - if ( $PassThru ) { - $SubmitOrderResponse - } + } else { + $AddItemToCart | Out-String } } -} +} \ No newline at end of file diff --git a/ServiceNow/Public/Submit-ServiceNowCatalogOrder.ps1 b/ServiceNow/Public/Submit-ServiceNowCatalogOrder.ps1 new file mode 100644 index 0000000..bf45b33 --- /dev/null +++ b/ServiceNow/Public/Submit-ServiceNowCatalogOrder.ps1 @@ -0,0 +1,63 @@ +<# +.SYNOPSIS + Submit a catalog request using Service Catalog API + +.DESCRIPTION + Checks out the user cart, based on the current check-out type (one-step or two-step). Reference: https://developer.servicenow.com/dev.do#!/reference/api/zurich/rest/c_ServiceCatalogAPI#servicecat-POST-cart-sub_order?navFilter=serv + +.PARAMETER PassThru + If provided, the new record will be returned + +.PARAMETER Connection + Azure Automation Connection object containing username, password, and URL for the ServiceNow instance + +.PARAMETER ServiceNowSession + ServiceNow session created by New-ServiceNowSession. Will default to script-level variable $ServiceNowSession. + +.EXAMPLE + Submit-ServiceNowCatalogOrder + + Checks out the user cart, based on the current check-out type (one-step or two-step). + +.EXAMPLE + Submit-ServiceNowCatalogOrder -PassThru + + Checks out the user cart, based on the current check-out type (one-step or two-step) and returns the request numbers as an object. + +.INPUTS + InputData + +.OUTPUTS + PSCustomObject if PassThru provided +#> +function Submit-ServiceNowCatalogOrder { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter()][Hashtable]$Connection, + [Parameter()][hashtable]$ServiceNowSession = $script:ServiceNowSession, + [Parameter()][switch]$PassThru + ) + + process { + + if ( $PSCmdlet.ShouldProcess('POST cart to Submit_Order API') ) { + $SubmitOrder = @{ + Method = 'Post' + UriLeaf = "/servicecatalog/cart/submit_order" + Namespace = 'sn_sc' + Connection = $Connection + ServiceNowSession = $ServiceNowSession + } + + $SubmitOrderResponse = Invoke-ServiceNowRestMethod @SubmitOrder + + if ($PassThru) { + $SubmitOrderResponse | Select-Object @{'n' = 'number'; 'e' = { $_.request_number } }, request_id + } + + } else { + Write-Output "Checks out the user cart, based on the current check-out type (one-step or two-step).`n`nIf one-step checkout, the method checks out (saves) the cart and returns the request number and the request order ID. If two-step checkout, the method returns the cart order status and all the information required for two-step checkout." + } + } +} \ No newline at end of file