Skip to content

Commit

Permalink
Merge pull request #145 from Snow-Shell/direct-cred-removal
Browse files Browse the repository at this point in the history
v3 - major update
  • Loading branch information
gdbarron authored Jul 12, 2021
2 parents 6969427 + 95c295e commit b979c4b
Show file tree
Hide file tree
Showing 36 changed files with 824 additions and 1,272 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
## 3.0
- New functionality in `Get-ServiceNowRecord`
- Add `Id` property to easily retrieve a record by either number or sysid.
- 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`.
- 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'.
- 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.
- Add alias `gsnr`. With the above change, a Get can be as simple as `gsnr inc0010001`.
- Add autocomplete for `Table` parameter in `Add-ServiceNowAttachment` and `Get-ServiceNowAttachment`.
- 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.
- 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.
- Add advanced filtering and sorting functionality to `Get-ServiceNowAttachment` which can be really useful when searching across the attachments table.
- Convert access and refresh tokens in $ServiceNowSession from plain text to a credential for added security.
- Pipeline enhancements added in many places.
- Add Change Task and Attachments to formats.
- `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.
- 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.
- 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.
- ***Breaking change:*** rename `Get-ServiceNowAttachmentDetail` to `Get-ServiceNowAttachment`.
- ***Breaking change:*** rename `Get-ServiceNowAttachment` to `Export-ServiceNowAttachment`.
- ***Breaking change:*** `Get-ServiceNowTable` and `Get-ServiceNowTableEntry` have been deprecated. Use `Get-ServiceNowRecord`.

## 2.4.2
- 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

Expand Down
78 changes: 39 additions & 39 deletions ServiceNow/Private/Get-ServiceNowAuth.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
function Get-ServiceNowAuth {
<#
.SYNOPSIS
Return hashtable with base Uri and auth info. Add uri leaf, body, etc to output.
.DESCRIPTION
.INPUTS
None
Expand All @@ -12,54 +13,53 @@ function Get-ServiceNowAuth {
[CmdletBinding()]
Param (
[Parameter()]
[PSCredential] $Credential,

[Parameter()]
[string] $ServiceNowURL,

[Parameter()]
[Alias('C')]
[hashtable] $Connection,

[Parameter()]
[hashtable] $ServiceNowSession = $script:ServiceNowSession
[Alias('S')]
[hashtable] $ServiceNowSession
)

$hashOut = @{}
begin {
$hashOut = @{}
}

# Get credential and ServiceNow REST URL
if ( $ServiceNowSession.Count -gt 0 ) {
$hashOut.Uri = $ServiceNowSession.BaseUri
if ( $ServiceNowSession.AccessToken ) {
$hashOut.Headers = @{
'Authorization' = 'Bearer {0}' -f $ServiceNowSession.AccessToken
}
} else {
$hashOut.Credential = $ServiceNowSession.Credential
}
process {

if ( $ServiceNowSession.Proxy ) {
$hashOut.Proxy = $ServiceNowSession.Proxy
if ( $ServiceNowSession.ProxyCredential ) {
$hashOut.ProxyCredential = $ServiceNowSession.ProxyCredential
} else {
$hashOut.ProxyUseDefaultCredentials = $true
if ( $ServiceNowSession.Count -gt 0 ) {
$hashOut.Uri = $ServiceNowSession.BaseUri
if ( $ServiceNowSession.AccessToken ) {
$hashOut.Headers = @{
'Authorization' = 'Bearer {0}' -f $ServiceNowSession.AccessToken.GetNetworkCredential().password
}
}
else {
$hashOut.Credential = $ServiceNowSession.Credential
}

if ( $ServiceNowSession.Proxy ) {
$hashOut.Proxy = $ServiceNowSession.Proxy
if ( $ServiceNowSession.ProxyCredential ) {
$hashOut.ProxyCredential = $ServiceNowSession.ProxyCredential
}
else {
$hashOut.ProxyUseDefaultCredentials = $true
}
}
}

} elseif ( $Credential -and $ServiceNowURL ) {
Write-Warning -Message 'This authentication path, providing URL and credential directly, will be deprecated in a future release. Please use New-ServiceNowSession.'
$hashOut.Uri = 'https://{0}/api/now/v1' -f $ServiceNowURL
$hashOut.Credential = $Credential

} elseif ( $Connection ) {
$SecurePassword = ConvertTo-SecureString $Connection.Password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($Connection.Username, $SecurePassword)
$hashOut.Credential = $Credential
$hashOut.Uri = 'https://{0}/api/now/v1' -f $Connection.ServiceNowUri

} else {
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"
elseif ( $Connection ) {
Write-Verbose 'connection'
$SecurePassword = ConvertTo-SecureString $Connection.Password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($Connection.Username, $SecurePassword)
$hashOut.Credential = $Credential
$hashOut.Uri = 'https://{0}/api/now/v1' -f $Connection.ServiceNowUri
} else {
throw "You must authenticate by either calling the New-ServiceNowSession cmdlet or passing in an Azure Automation connection object"
}
}

$hashOut
end {
$hashOut
}
}
34 changes: 16 additions & 18 deletions ServiceNow/Private/Invoke-ServiceNowRestMethod.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,15 @@ function Invoke-ServiceNowRestMethod {
[Alias('DisplayValues')]
[string] $DisplayValue = 'true',

[Parameter()]
[PSCredential] $Credential,

[Parameter()]
[string] $ServiceNowUrl,

[Parameter()]
[hashtable] $Connection,

[Parameter()]
[hashtable] $ServiceNowSession = $script:ServiceNowSession
)

$getAuth = @{
Credential = $Credential
ServiceNowUrl = $ServiceNowUrl
Connection = $Connection
ServiceNowSession = $ServiceNowSession
}
$params = Get-ServiceNowAuth @getAuth
# get header/body auth values
$params = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession

$params.Method = $Method
$params.ContentType = 'application/json'
Expand All @@ -89,7 +78,7 @@ function Invoke-ServiceNowRestMethod {
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 -eq $Table -or $_.ClassName -eq $Table } | Select-Object -ExpandProperty 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
Expand Down Expand Up @@ -160,12 +149,21 @@ function Invoke-ServiceNowRestMethod {

$response = Invoke-WebRequest @params

$content = $response.content | ConvertFrom-Json
if ( $content.PSobject.Properties.Name -contains "result" ) {
$records = @($content | Select-Object -ExpandProperty result)
# 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 {
$records = @($content)
# 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
Expand Down
84 changes: 37 additions & 47 deletions ServiceNow/Public/Add-ServiceNowAttachment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,18 @@ Function Add-ServiceNowAttachment {
#>

[OutputType([PSCustomObject[]])]
[CmdletBinding(DefaultParameterSetName = 'Session', SupportsShouldProcess)]
[CmdletBinding(SupportsShouldProcess)]
Param(
# Table containing the entry
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('sys_class_name')]
[string]$Table,
[string] $Table,

# Object number
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string] $Number,
[Alias('sys_id', 'SysId', 'number')]
[string] $Id,

# Filter results by file name
[parameter(Mandatory)]
[Parameter(Mandatory)]
[ValidateScript( {
Test-Path $_
})]
Expand All @@ -62,57 +61,41 @@ Function Add-ServiceNowAttachment {
[Parameter()]
[string] $ContentType,

# Credential used to authenticate to ServiceNow
[Parameter(ParameterSetName = 'SpecifyConnectionFields', Mandatory)]
[ValidateNotNullOrEmpty()]
[Alias('ServiceNowCredential')]
[PSCredential] $Credential,

# The URL for the ServiceNow instance being used
[Parameter(ParameterSetName = 'SpecifyConnectionFields', Mandatory)]
[ValidateScript( { $_ | Test-ServiceNowURL })]
[ValidateNotNullOrEmpty()]
[Alias('Url')]
[string] $ServiceNowURL,
# Allow the results to be shown
[Parameter()]
[switch] $PassThru,

# Azure Automation Connection object containing username, password, and URL for the ServiceNow instance
[Parameter(ParameterSetName = 'UseConnectionObject', Mandatory)]
[ValidateNotNullOrEmpty()]
[Parameter()]
# [ValidateNotNullOrEmpty()]
[Hashtable] $Connection,

[Parameter(ParameterSetName = 'Session')]
[ValidateNotNullOrEmpty()]
[hashtable] $ServiceNowSession = $script:ServiceNowSession,

# Allow the results to be shown
[Parameter()]
[switch] $PassThru
# [ValidateNotNullOrEmpty()]
[hashtable] $ServiceNowSession = $script:ServiceNowSession
)

begin {}

process {

$getSysIdParams = @{
Table = $Table
Query = (New-ServiceNowQuery -Filter @('number', '-eq', $number))
Properties = 'sys_id'
$getParams = @{
Id = $Id
Property = 'sys_class_name', 'sys_id', 'number'
Connection = $Connection
Credential = $Credential
ServiceNowUrl = $ServiceNowURL
ServiceNowSession = $ServiceNowSession
}
if ( $Table ) {
$getParams.Table = $Table
}
$tableRecord = Get-ServiceNowRecord @getParams

# Use the number and table to determine the sys_id
$sysId = Invoke-ServiceNowRestMethod @getSysIdParams | Select-Object -ExpandProperty sys_id

$getAuth = @{
Credential = $Credential
ServiceNowUrl = $ServiceNowUrl
Connection = $Connection
ServiceNowSession = $ServiceNowSession
if ( -not $tableRecord ) {
Write-Error "Record not found for Id '$Id'"
continue
}
$auth = Get-ServiceNowAuth @getAuth

$auth = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession

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

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

If ($PassThru) {
$response.result
if ( $response.Content ) {
if ( $PassThru.IsPresent ) {
$content = $response.content | ConvertFrom-Json
$content.result
}
}
else {
# invoke-webrequest didn't throw an error, but we didn't get content back either
throw ('"{0} : {1}' -f $response.StatusCode, $response | Out-String )
}
}
}
Expand Down
Loading

0 comments on commit b979c4b

Please sign in to comment.