Skip to content

Commit b979c4b

Browse files
authored
Merge pull request #145 from Snow-Shell/direct-cred-removal
v3 - major update
2 parents 6969427 + 95c295e commit b979c4b

36 files changed

+824
-1272
lines changed

CHANGELOG.md

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
## 3.0
2+
- New functionality in `Get-ServiceNowRecord`
3+
- Add `Id` property to easily retrieve a record by either number or sysid.
4+
- Add `ParentId` property to easily retrieve records based on the parent number or sysid. For example, to retrieve catalog tasks associated with a requested item execute `Get-ServiceNowRecord -ParentId RITM01234567`.
5+
- Add `Description` property to retrieve records based on a table specific description field. For many tables this field will be short_description, but will be different for others. For example, when performing this against the 'User' table, the description field is 'Name'.
6+
- Add ability to provide a known prefixed `Id` without providing `Table`, `Get-ServiceNowRecord -Id inc0010001`. To see the list of known prefixes, execute `$ServiceNowTable.NumberPrefix` after importing the module.
7+
- Add alias `gsnr`. With the above change, a Get can be as simple as `gsnr inc0010001`.
8+
- Add autocomplete for `Table` parameter in `Add-ServiceNowAttachment` and `Get-ServiceNowAttachment`.
9+
- Add `Id` parameter to `Add-ServiceNowAttachment` and `Update-ServiceNowRecord` which accepts either number or sysid. Just as with `Get-ServiceNowRecord` you can now provide just `Id` if it has a known prefix.
10+
- Add ability to `Get-ServiceNowAttachment` to get attachments either via associated record or directly from the attachments table when you want to search all attachments.
11+
- Add advanced filtering and sorting functionality to `Get-ServiceNowAttachment` which can be really useful when searching across the attachments table.
12+
- Convert access and refresh tokens in $ServiceNowSession from plain text to a credential for added security.
13+
- Pipeline enhancements added in many places.
14+
- Add Change Task and Attachments to formats.
15+
- `Update-ServiceNowNumber` has been deprecated and the functionality has been added to `Update-ServiceNowRecord`. An alias has also been added so existing scripts do not break.
16+
- Prep for removal of all `Get-` functions except for `Get-ServiceNowRecord` and `Get-ServiceNowAttachment`. Table specific Get functions have been deprecated. `Get-ServiceNowRecordInterim` has been created and all table specific Get functions have been aliased so existing scripts do not break. Please start to migrate to `Get-ServiceNowRecord` as these functions will all be deprecated in the near future.
17+
- As communicated in v2.0, authentication cleanup has occurred. This involves removal of Credential/Url authentication in each function in favor of `ServiceNowSession`. You can still authenticate with Credential/Url, but must use `New-ServiceNowSession`. `Set-ServiceNowAuth`, `Remove-ServiceNowAuth`, and `Test-ServiceNowAuthIsSet` have been deprecated.
18+
- ***Breaking change:*** rename `Get-ServiceNowAttachmentDetail` to `Get-ServiceNowAttachment`.
19+
- ***Breaking change:*** rename `Get-ServiceNowAttachment` to `Export-ServiceNowAttachment`.
20+
- ***Breaking change:*** `Get-ServiceNowTable` and `Get-ServiceNowTableEntry` have been deprecated. Use `Get-ServiceNowRecord`.
21+
122
## 2.4.2
223
- Fix [#141](https://github.com/Snow-Shell/servicenow-powershell/issues/141), add `UseBasicParsing` to all API calls to keep AA from failing when IE hasn't been initialized
324

+39-39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
function Get-ServiceNowAuth {
22
<#
33
.SYNOPSIS
4+
Return hashtable with base Uri and auth info. Add uri leaf, body, etc to output.
45
.DESCRIPTION
56
.INPUTS
67
None
@@ -12,54 +13,53 @@ function Get-ServiceNowAuth {
1213
[CmdletBinding()]
1314
Param (
1415
[Parameter()]
15-
[PSCredential] $Credential,
16-
17-
[Parameter()]
18-
[string] $ServiceNowURL,
19-
20-
[Parameter()]
16+
[Alias('C')]
2117
[hashtable] $Connection,
2218

2319
[Parameter()]
24-
[hashtable] $ServiceNowSession = $script:ServiceNowSession
20+
[Alias('S')]
21+
[hashtable] $ServiceNowSession
2522
)
2623

27-
$hashOut = @{}
24+
begin {
25+
$hashOut = @{}
26+
}
2827

29-
# Get credential and ServiceNow REST URL
30-
if ( $ServiceNowSession.Count -gt 0 ) {
31-
$hashOut.Uri = $ServiceNowSession.BaseUri
32-
if ( $ServiceNowSession.AccessToken ) {
33-
$hashOut.Headers = @{
34-
'Authorization' = 'Bearer {0}' -f $ServiceNowSession.AccessToken
35-
}
36-
} else {
37-
$hashOut.Credential = $ServiceNowSession.Credential
38-
}
28+
process {
3929

40-
if ( $ServiceNowSession.Proxy ) {
41-
$hashOut.Proxy = $ServiceNowSession.Proxy
42-
if ( $ServiceNowSession.ProxyCredential ) {
43-
$hashOut.ProxyCredential = $ServiceNowSession.ProxyCredential
44-
} else {
45-
$hashOut.ProxyUseDefaultCredentials = $true
30+
if ( $ServiceNowSession.Count -gt 0 ) {
31+
$hashOut.Uri = $ServiceNowSession.BaseUri
32+
if ( $ServiceNowSession.AccessToken ) {
33+
$hashOut.Headers = @{
34+
'Authorization' = 'Bearer {0}' -f $ServiceNowSession.AccessToken.GetNetworkCredential().password
35+
}
36+
}
37+
else {
38+
$hashOut.Credential = $ServiceNowSession.Credential
39+
}
40+
41+
if ( $ServiceNowSession.Proxy ) {
42+
$hashOut.Proxy = $ServiceNowSession.Proxy
43+
if ( $ServiceNowSession.ProxyCredential ) {
44+
$hashOut.ProxyCredential = $ServiceNowSession.ProxyCredential
45+
}
46+
else {
47+
$hashOut.ProxyUseDefaultCredentials = $true
48+
}
4649
}
4750
}
48-
49-
} elseif ( $Credential -and $ServiceNowURL ) {
50-
Write-Warning -Message 'This authentication path, providing URL and credential directly, will be deprecated in a future release. Please use New-ServiceNowSession.'
51-
$hashOut.Uri = 'https://{0}/api/now/v1' -f $ServiceNowURL
52-
$hashOut.Credential = $Credential
53-
54-
} elseif ( $Connection ) {
55-
$SecurePassword = ConvertTo-SecureString $Connection.Password -AsPlainText -Force
56-
$Credential = New-Object System.Management.Automation.PSCredential ($Connection.Username, $SecurePassword)
57-
$hashOut.Credential = $Credential
58-
$hashOut.Uri = 'https://{0}/api/now/v1' -f $Connection.ServiceNowUri
59-
60-
} else {
61-
throw "Exception: You must do one of the following to authenticate: `n 1. Call the New-ServiceNowSession cmdlet `n 2. Pass in an Azure Automation connection object `n 3. Pass in an endpoint and credential"
51+
elseif ( $Connection ) {
52+
Write-Verbose 'connection'
53+
$SecurePassword = ConvertTo-SecureString $Connection.Password -AsPlainText -Force
54+
$Credential = New-Object System.Management.Automation.PSCredential ($Connection.Username, $SecurePassword)
55+
$hashOut.Credential = $Credential
56+
$hashOut.Uri = 'https://{0}/api/now/v1' -f $Connection.ServiceNowUri
57+
} else {
58+
throw "You must authenticate by either calling the New-ServiceNowSession cmdlet or passing in an Azure Automation connection object"
59+
}
6260
}
6361

64-
$hashOut
62+
end {
63+
$hashOut
64+
}
6565
}

ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1

+16-18
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,15 @@ function Invoke-ServiceNowRestMethod {
6161
[Alias('DisplayValues')]
6262
[string] $DisplayValue = 'true',
6363

64-
[Parameter()]
65-
[PSCredential] $Credential,
66-
67-
[Parameter()]
68-
[string] $ServiceNowUrl,
69-
7064
[Parameter()]
7165
[hashtable] $Connection,
7266

7367
[Parameter()]
7468
[hashtable] $ServiceNowSession = $script:ServiceNowSession
7569
)
7670

77-
$getAuth = @{
78-
Credential = $Credential
79-
ServiceNowUrl = $ServiceNowUrl
80-
Connection = $Connection
81-
ServiceNowSession = $ServiceNowSession
82-
}
83-
$params = Get-ServiceNowAuth @getAuth
71+
# get header/body auth values
72+
$params = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession
8473

8574
$params.Method = $Method
8675
$params.ContentType = 'application/json'
@@ -89,7 +78,7 @@ function Invoke-ServiceNowRestMethod {
8978
if ( $Table ) {
9079
# table can either be the actual table name or class name
9180
# look up the actual table name
92-
$tableName = $script:ServiceNowTable | Where-Object { $_.Name -eq $Table -or $_.ClassName -eq $Table } | Select-Object -ExpandProperty Name
81+
$tableName = $script:ServiceNowTable | Where-Object { $_.Name.ToLower() -eq $Table.ToLower() -or $_.ClassName.ToLower() -eq $Table.ToLower() } | Select-Object -ExpandProperty Name
9382
# if not in our lookup, just use the table name as provided
9483
if ( -not $tableName ) {
9584
$tableName = $Table
@@ -160,12 +149,21 @@ function Invoke-ServiceNowRestMethod {
160149

161150
$response = Invoke-WebRequest @params
162151

163-
$content = $response.content | ConvertFrom-Json
164-
if ( $content.PSobject.Properties.Name -contains "result" ) {
165-
$records = @($content | Select-Object -ExpandProperty result)
152+
# TODO: this could use some work
153+
# checking for content is good, but at times we'll get content that's not valid
154+
# eg. html content when a dev instance is hibernating
155+
if ( $response.Content ) {
156+
$content = $response.content | ConvertFrom-Json
157+
if ( $content.PSobject.Properties.Name -contains "result" ) {
158+
$records = @($content | Select-Object -ExpandProperty result)
159+
}
160+
else {
161+
$records = @($content)
162+
}
166163
}
167164
else {
168-
$records = @($content)
165+
# invoke-webrequest didn't throw an error per se, but we didn't get content back either
166+
throw ('"{0} : {1}' -f $response.StatusCode, $response | Out-String )
169167
}
170168

171169
$totalRecordCount = 0

ServiceNow/Public/Add-ServiceNowAttachment.ps1

+37-47
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,18 @@ Function Add-ServiceNowAttachment {
4040
#>
4141

4242
[OutputType([PSCustomObject[]])]
43-
[CmdletBinding(DefaultParameterSetName = 'Session', SupportsShouldProcess)]
43+
[CmdletBinding(SupportsShouldProcess)]
4444
Param(
4545
# Table containing the entry
46-
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
46+
[Parameter(ValueFromPipelineByPropertyName)]
4747
[Alias('sys_class_name')]
48-
[string]$Table,
48+
[string] $Table,
4949

50-
# Object number
5150
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
52-
[string] $Number,
51+
[Alias('sys_id', 'SysId', 'number')]
52+
[string] $Id,
5353

54-
# Filter results by file name
55-
[parameter(Mandatory)]
54+
[Parameter(Mandatory)]
5655
[ValidateScript( {
5756
Test-Path $_
5857
})]
@@ -62,57 +61,41 @@ Function Add-ServiceNowAttachment {
6261
[Parameter()]
6362
[string] $ContentType,
6463

65-
# Credential used to authenticate to ServiceNow
66-
[Parameter(ParameterSetName = 'SpecifyConnectionFields', Mandatory)]
67-
[ValidateNotNullOrEmpty()]
68-
[Alias('ServiceNowCredential')]
69-
[PSCredential] $Credential,
70-
71-
# The URL for the ServiceNow instance being used
72-
[Parameter(ParameterSetName = 'SpecifyConnectionFields', Mandatory)]
73-
[ValidateScript( { $_ | Test-ServiceNowURL })]
74-
[ValidateNotNullOrEmpty()]
75-
[Alias('Url')]
76-
[string] $ServiceNowURL,
64+
# Allow the results to be shown
65+
[Parameter()]
66+
[switch] $PassThru,
7767

7868
# Azure Automation Connection object containing username, password, and URL for the ServiceNow instance
79-
[Parameter(ParameterSetName = 'UseConnectionObject', Mandatory)]
80-
[ValidateNotNullOrEmpty()]
69+
[Parameter()]
70+
# [ValidateNotNullOrEmpty()]
8171
[Hashtable] $Connection,
8272

83-
[Parameter(ParameterSetName = 'Session')]
84-
[ValidateNotNullOrEmpty()]
85-
[hashtable] $ServiceNowSession = $script:ServiceNowSession,
86-
87-
# Allow the results to be shown
8873
[Parameter()]
89-
[switch] $PassThru
74+
# [ValidateNotNullOrEmpty()]
75+
[hashtable] $ServiceNowSession = $script:ServiceNowSession
9076
)
9177

9278
begin {}
9379

9480
process {
9581

96-
$getSysIdParams = @{
97-
Table = $Table
98-
Query = (New-ServiceNowQuery -Filter @('number', '-eq', $number))
99-
Properties = 'sys_id'
82+
$getParams = @{
83+
Id = $Id
84+
Property = 'sys_class_name', 'sys_id', 'number'
10085
Connection = $Connection
101-
Credential = $Credential
102-
ServiceNowUrl = $ServiceNowURL
10386
ServiceNowSession = $ServiceNowSession
10487
}
88+
if ( $Table ) {
89+
$getParams.Table = $Table
90+
}
91+
$tableRecord = Get-ServiceNowRecord @getParams
10592

106-
# Use the number and table to determine the sys_id
107-
$sysId = Invoke-ServiceNowRestMethod @getSysIdParams | Select-Object -ExpandProperty sys_id
108-
109-
$getAuth = @{
110-
Credential = $Credential
111-
ServiceNowUrl = $ServiceNowUrl
112-
Connection = $Connection
113-
ServiceNowSession = $ServiceNowSession
93+
if ( -not $tableRecord ) {
94+
Write-Error "Record not found for Id '$Id'"
95+
continue
11496
}
115-
$auth = Get-ServiceNowAuth @getAuth
97+
98+
$auth = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession
11699

117100
ForEach ($Object in $File) {
118101
$FileData = Get-ChildItem $Object -ErrorAction Stop
@@ -128,20 +111,27 @@ Function Add-ServiceNowAttachment {
128111
# POST: https://instance.service-now.com/api/now/attachment/file?table_name=incident&table_sys_id=d71f7935c0a8016700802b64c67c11c6&file_name=Issue_screenshot
129112
# $Uri = "{0}/file?table_name={1}&table_sys_id={2}&file_name={3}" -f $ApiUrl, $Table, $TableSysID, $FileData.Name
130113
$invokeRestMethodSplat = $auth
131-
$invokeRestMethodSplat.Uri += '/attachment/file?table_name={0}&table_sys_id={1}&file_name={2}' -f $Table, $sysId, $FileData.Name
114+
$invokeRestMethodSplat.Uri += '/attachment/file?table_name={0}&table_sys_id={1}&file_name={2}' -f $tableRecord.sys_class_name, $tableRecord.sys_id, $FileData.Name
132115
$invokeRestMethodSplat.Headers += @{'Content-Type' = $ContentType }
133116
$invokeRestMethodSplat.UseBasicParsing = $true
134117
$invokeRestMethodSplat += @{
135118
Method = 'POST'
136119
InFile = $FileData.FullName
137120
}
138121

139-
If ($PSCmdlet.ShouldProcess("$Table $Number", 'Add attachment')) {
122+
If ($PSCmdlet.ShouldProcess(('{0} {1}' -f $tableRecord.sys_class_name, $tableRecord.number), ('Add attachment {0}' -f $FileData.FullName))) {
140123
Write-Verbose ($invokeRestMethodSplat | ConvertTo-Json)
141-
$response = Invoke-RestMethod @invokeRestMethodSplat
124+
$response = Invoke-WebRequest @invokeRestMethodSplat
142125

143-
If ($PassThru) {
144-
$response.result
126+
if ( $response.Content ) {
127+
if ( $PassThru.IsPresent ) {
128+
$content = $response.content | ConvertFrom-Json
129+
$content.result
130+
}
131+
}
132+
else {
133+
# invoke-webrequest didn't throw an error, but we didn't get content back either
134+
throw ('"{0} : {1}' -f $response.StatusCode, $response | Out-String )
145135
}
146136
}
147137
}

0 commit comments

Comments
 (0)