-
Notifications
You must be signed in to change notification settings - Fork 171
Add Additional Namespace & Catalog Request Support #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 8 commits
fe9b6b8
ba19c85
8869f17
86d60f2
ec233ed
1f61779
92530f7
4295689
38dc592
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 ) { | ||
Comment on lines
-84
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please try and keep formatting the same :) |
||
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" | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -17,7 +17,7 @@ function Invoke-ServiceNowRestMethod { | |||||||||||||
[CmdletBinding(SupportsPaging)] | ||||||||||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseBOMForUnicodeEncodedFile', '', Justification = 'issuees with *nix machines and no benefit')] | ||||||||||||||
|
||||||||||||||
Param ( | ||||||||||||||
param ( | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to change |
||||||||||||||
[parameter()] | ||||||||||||||
[ValidateSet('Get', 'Post', 'Patch', 'Delete')] | ||||||||||||||
[string] $Method = 'Get', | ||||||||||||||
|
@@ -48,6 +48,9 @@ function Invoke-ServiceNowRestMethod { | |||||||||||||
[parameter()] | ||||||||||||||
[string] $FilterString, | ||||||||||||||
|
||||||||||||||
[parameter()] | ||||||||||||||
[string] $Namespace, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. default to 'now'? |
||||||||||||||
|
||||||||||||||
[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 | ||||||||||||||
} | ||||||||||||||
Comment on lines
+80
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
$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,25 +217,22 @@ 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 $_ | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
$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 | ||||||||||||||
} | ||||||||||||||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might be good to have a way to control when checkout occurs in case folks want to add multiple (different) items before submitting order |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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' } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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' } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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, | ||||||
Comment on lines
+46
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about we combine this into one |
||||||
[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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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 } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should make quantity a function parameter along with the other request body parameters, sysparm_also_request_for and sysparm_requested_for. https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/c_ServiceCatalogAPI.html#title_servicecat-POST-items-add_to_cart. add examples for these new parameters as well. |
||||||
Namespace = 'sn_sc' | ||||||
Connection = $Connection | ||||||
ServiceNowSession = $ServiceNowSession | ||||||
} | ||||||
|
||||||
if ( $PSCmdlet.ShouldProcess($CatalogItemID, 'Create new catalog item request') ) { | ||||||
|
||||||
$AddItemCartResponse = Invoke-ServiceNowRestMethod @AddItemToCart | ||||||
|
||||||
if ($AddItemCartResponse.cart_id) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why might we not get a value for cart_id that we need this 'if'? |
||||||
$SubmitOrder = @{ | ||||||
Method = 'Post' | ||||||
UriLeaf = "/servicecatalog/cart/submit_order" | ||||||
Namespace = 'sn_sc' | ||||||
Connection = $Connection | ||||||
ServiceNowSession = $ServiceNowSession | ||||||
} | ||||||
|
||||||
$SubmitOrderResponse = Invoke-ServiceNowRestMethod @SubmitOrder | ||||||
} | ||||||
if ( $PassThru ) { | ||||||
$SubmitOrderResponse | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
consider updating the output to something like the above so it can be piped directly into the other functions, eg. Get-ServiceNowRecord |
||||||
} | ||||||
Comment on lines
+92
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you are going to keep the check for cart_id, this needs to move inside the 'if' block otherwise $SubmitOrderResponse would be null |
||||||
} | ||||||
} | ||||||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll take care of updating this file when I perform the release. Can you please undo these changes? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking we're better served by changing the baseuri to end with /api and just append the namespace. this will involve changes here and invoke-service nowrestmethod.