Public/Admin.ps1
|
function Get-SnowRecord { <# .SYNOPSIS Gets records from any ServiceNow table. .DESCRIPTION Reads records through the Table API. Each result is a PSCustomObject whose type name is Snowplow.Record.<table>. By default at most 100 records are returned; use -Limit to change the cap or -All to page through every matching record. Reference fields are returned the way the Table API returns them. Use -Fields to keep payloads small on wide tables such as incident or cmdb_ci. .PARAMETER Table The internal table name, such as incident, change_request or sys_user. Tab completion uses the cache filled by Get-SnowTable. .PARAMETER Query A ServiceNow encoded query. Build one with New-SnowQuery or pass a raw string such as 'active=true^priority=1'. .PARAMETER Fields The fields to return. Tab completion uses the cache filled by Get-SnowDictionary. .PARAMETER Limit The maximum number of records to return (1-10000, default 100). Ignored when -All is used. .PARAMETER All Pages through every matching record instead of stopping at -Limit. .PARAMETER Backend Rest, Sdk or Auto. Auto currently selects Rest. .EXAMPLE Get-SnowRecord -Table incident -Query 'active=true^priority=1' -Fields number,short_description,assigned_to -Limit 25 Lists the 25 most recent active P1 incidents with only the columns you care about. .EXAMPLE Get-SnowRecord -Table incident -Query 'number=INC0010001' Looks up a single incident by number. .EXAMPLE $query = New-SnowQuery | Where-SnowField sys_created_on -Operator ([string][char]62) -Value (New-SnowRelativeDate -Last 7 -Unit Days) Get-SnowRecord -Table change_request -Query $query -All | Group-Object state | Select-Object Name, Count Counts every change request raised in the last week by state. .EXAMPLE Get-SnowRecord -Table sys_user -Query 'active=true^last_login<javascript:gs.daysAgoStart(90)' -All | Select-Object user_name, name, last_login Finds active users who have not logged in for 90 days, ready for a licence clean-up review. .EXAMPLE Get-SnowRecord -Table incident -Limit 1 | Get-SnowLink -Format Markdown Pipes a record into Get-SnowLink to produce a link you can paste into a chat or ticket. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory, Position = 0)] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete) & (Get-Module Snowplow) { param($word) Get-SnowCompletionValue -Kind Table -Word $word } $wordToComplete })] [ValidatePattern('^[a-z0-9_]+$')][string]$Table, [string]$Query, [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) & (Get-Module Snowplow) { param($table, $word) Get-SnowCompletionValue -Kind Field -Table $table -Word $word } $fakeBoundParameters.Table $wordToComplete })] [string[]]$Fields, [ValidateRange(1, 10000)][int]$Limit = 100, [switch]$All, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $request = @{ Table = $Table Query = $Query Fields = $Fields PageSize = [Math]::Min($Limit, 1000) Backend = $Backend } if (-not $All) { $request.First = $Limit } $records = Invoke-SnowBackendRequest @request foreach ($record in $records) { if ($record -is [System.Collections.IDictionary]) { $record = [pscustomobject]$record } if ($record -is [pscustomobject]) { $record.PSTypeNames.Insert(0, "Snowplow.Record.$Table") } $record } } function New-SnowRecord { <# .SYNOPSIS Creates a record in a ServiceNow table. .DESCRIPTION Sends a POST to the Table API and returns the created record. Supports -WhatIf and -Confirm. Prod-tagged profiles are read-only unless you connected with -AllowWrite, and always prompt for confirmation. .PARAMETER Table The internal table name to create the record in. .PARAMETER Values A hashtable of field names and values. Use the same internal field names you would see in the form. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE New-SnowRecord -Table incident -Values @{ short_description = 'Printer offline on level 3'; urgency = '2'; impact = '2' } Logs a new incident. .EXAMPLE New-SnowRecord -Table incident -Values @{ short_description = 'Test' } -WhatIf Shows what would be created without sending the request. Use this before running a new script against a shared instance. .EXAMPLE Import-Csv .\new-users.csv | ForEach-Object { New-SnowRecord -Table sys_user -Values @{ user_name = $_.UserName; first_name = $_.First; last_name = $_.Last; email = $_.Email } } Bulk-creates users from a CSV file with columns UserName, First, Last and Email. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [OutputType([pscustomobject])] param( [Parameter(Mandatory, Position = 0)] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete) & (Get-Module Snowplow) { param($word) Get-SnowCompletionValue -Kind Table -Word $word } $wordToComplete })] [ValidatePattern('^[a-z0-9_]+$')][string]$Table, [Parameter(Mandatory, Position = 1)][System.Collections.IDictionary]$Values, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $previousConfirmPreference = $ConfirmPreference try { $session = Get-SnowSession if ($session.Environment -eq 'Prod') { if ($session.ReadOnly) { throw [System.InvalidOperationException]::new("Writes are disabled for Prod profile '$($session.Profile)'. Reconnect with -AllowWrite to enable writes.") } $ConfirmPreference = 'Low' } if ($PSCmdlet.ShouldProcess($Table, 'Create record')) { $record = Invoke-SnowBackendRequest -Table $Table -Method POST -Body $Values -Backend $Backend if ($record -is [pscustomobject]) { $record.PSTypeNames.Insert(0, "Snowplow.Record.$Table") } $record } } finally { $ConfirmPreference = $previousConfirmPreference } } function Set-SnowRecord { <# .SYNOPSIS Updates a ServiceNow record. .DESCRIPTION Updates by sys_id. Uses PATCH so only the supplied fields change, unless -Replace selects PUT. Supports -WhatIf and -Confirm. Prod-tagged profiles require -AllowWrite at connect time and always prompt for confirmation. .PARAMETER Table The internal table name. .PARAMETER SysId The 32-character sys_id of the record. .PARAMETER Values A hashtable of fields to change. .PARAMETER Replace Uses PUT instead of PATCH. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE Set-SnowRecord -Table incident -SysId 9d385017c611228701d22104cc95c371 -Values @{ priority = '2' } Raises or lowers the priority of one incident. .EXAMPLE $incident = Get-SnowRecord -Table incident -Query 'number=INC0010001' -Fields sys_id Set-SnowRecord -Table incident -SysId $incident.sys_id -Values @{ state = '6'; close_code = 'Solved (Permanently)'; close_notes = 'Replaced toner.' } Resolves an incident by looking up its sys_id from the number first. .EXAMPLE $serviceDeskGroups = @(Get-SnowRecord -Table sys_user_group -Query 'name=Service Desk' -Fields sys_id -Limit 2) if ($serviceDeskGroups.Count -ne 1) { throw "Expected exactly one Service Desk group, found $($serviceDeskGroups.Count)." } $assignmentGroupId = $serviceDeskGroups[0].sys_id Get-SnowRecord -Table incident -Query 'active=true^assigned_to=' -Fields sys_id | ForEach-Object { Set-SnowRecord -Table incident -SysId $_.sys_id -Values @{ assignment_group = $assignmentGroupId } -WhatIf } Previews reassigning every unassigned incident to the Service Desk group. Remove -WhatIf to apply. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [OutputType([pscustomobject])] param( [Parameter(Mandatory, Position = 0)] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete) & (Get-Module Snowplow) { param($word) Get-SnowCompletionValue -Kind Table -Word $word } $wordToComplete })] [ValidatePattern('^[a-z0-9_]+$')][string]$Table, [Parameter(Mandatory, Position = 1)][ValidatePattern('^[0-9a-fA-F]{32}$')][string]$SysId, [Parameter(Mandatory, Position = 2)][System.Collections.IDictionary]$Values, [switch]$Replace, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $method = if ($Replace) { 'PUT' } else { 'PATCH' } $previousConfirmPreference = $ConfirmPreference try { $session = Get-SnowSession if ($session.Environment -eq 'Prod') { if ($session.ReadOnly) { throw [System.InvalidOperationException]::new("Writes are disabled for Prod profile '$($session.Profile)'. Reconnect with -AllowWrite to enable writes.") } $ConfirmPreference = 'Low' } if ($PSCmdlet.ShouldProcess("$Table/$SysId", 'Update record')) { $record = Invoke-SnowBackendRequest -Table $Table -SysId $SysId -Method $method -Body $Values -Backend $Backend if ($record -is [pscustomobject]) { $record.PSTypeNames.Insert(0, "Snowplow.Record.$Table") } $record } } finally { $ConfirmPreference = $previousConfirmPreference } } function Remove-SnowRecord { <# .SYNOPSIS Deletes a ServiceNow record. .DESCRIPTION Deletes by sys_id. This is destructive and high impact, so it prompts for confirmation. Always try -WhatIf first. Prod-tagged profiles require -AllowWrite at connect time. .PARAMETER Table The internal table name. .PARAMETER SysId The 32-character sys_id of the record to delete. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE Remove-SnowRecord -Table incident -SysId 9d385017c611228701d22104cc95c371 -WhatIf Shows what would be deleted. .EXAMPLE Get-SnowRecord -Table sys_user -Query 'user_nameSTARTSWITHtest.' -Fields sys_id | ForEach-Object { Remove-SnowRecord -Table sys_user -SysId $_.sys_id -Confirm:$false } Cleans up test accounts left behind by an automation run on a sub-production instance. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [Parameter(Mandatory, Position = 0)] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete) & (Get-Module Snowplow) { param($word) Get-SnowCompletionValue -Kind Table -Word $word } $wordToComplete })] [ValidatePattern('^[a-z0-9_]+$')][string]$Table, [Parameter(Mandatory, Position = 1)][ValidatePattern('^[0-9a-fA-F]{32}$')][string]$SysId, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $previousConfirmPreference = $ConfirmPreference try { $session = Get-SnowSession if ($session.Environment -eq 'Prod') { if ($session.ReadOnly) { throw [System.InvalidOperationException]::new("Writes are disabled for Prod profile '$($session.Profile)'. Reconnect with -AllowWrite to enable writes.") } $ConfirmPreference = 'Low' } if ($PSCmdlet.ShouldProcess("$Table/$SysId", 'Delete record')) { $null = Invoke-SnowBackendRequest -Table $Table -SysId $SysId -Method DELETE -Backend $Backend } } finally { $ConfirmPreference = $previousConfirmPreference } } function Get-SnowTable { <# .SYNOPSIS Lists ServiceNow tables and their labels. .DESCRIPTION Queries sys_db_object. Results also populate the cache used for -Table tab completion. .PARAMETER Query An encoded query against sys_db_object, for example 'nameSTARTSWITHcmdb'. .PARAMETER Limit The maximum number of tables to return (default 100). .PARAMETER All Returns every matching table. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE Get-SnowTable -Query 'nameSTARTSWITHcmdb_ci' -All Discovers the CMDB class tables available on your instance. .EXAMPLE Get-SnowTable -All | Out-Null Fills the tab-completion cache so Get-SnowRecord -Table <Tab> completes your tables. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Query, [ValidateRange(1, 10000)][int]$Limit = 1000, [switch]$All, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $request = @{ Table = 'sys_db_object' Query = $Query Fields = @('sys_id', 'name', 'label', 'super_class') PageSize = [Math]::Min($Limit, 1000) Backend = $Backend } if (-not $All) { $request.First = $Limit } $tables = @(Invoke-SnowBackendRequest @request) if (-not $Query -and $All) { $script:SnowSchemaCache.Tables = $tables } $tables } function Get-SnowDictionary { <# .SYNOPSIS Gets field definitions from the ServiceNow dictionary. .DESCRIPTION Queries sys_dictionary for a table. Results populate the cache used by -Fields tab completion and by the query builder. .PARAMETER Table The table whose fields you want. .PARAMETER Element Limits the result to a single field (column) name. .PARAMETER Limit The maximum number of rows to return. .PARAMETER All Returns every field. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE Get-SnowDictionary -Table incident -All | Select-Object element, column_label, internal_type, max_length Lists every column on the incident table so you know the names to use in queries. .EXAMPLE Get-SnowDictionary -Table incident -Element priority Inspects one field. .EXAMPLE Get-SnowDictionary -Table x_acme_asset -All | Where-Object mandatory -eq 'true' | Select-Object element, column_label Finds the mandatory fields on a custom table before scripting inserts. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete) & (Get-Module Snowplow) { param($word) Get-SnowCompletionValue -Kind Table -Word $word } $wordToComplete })] [ValidatePattern('^[a-z0-9_]+$')][string]$Table, [string]$Element, [ValidateRange(1, 10000)][int]$Limit = 1000, [switch]$All, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $query = "name=$Table" if ($Element) { $query += "^element=$Element" } $request = @{ Table = 'sys_dictionary' Query = $query Fields = @('name', 'element', 'column_label', 'internal_type', 'reference', 'mandatory', 'read_only') PageSize = [Math]::Min($Limit, 1000) Backend = $Backend } if (-not $All) { $request.First = $Limit } $fields = @(Invoke-SnowBackendRequest @request) if (-not $Element -and $All) { $script:SnowSchemaCache.Fields[$Table] = $fields } $fields } function Get-SnowCI { <# .SYNOPSIS Gets configuration items from the CMDB. .DESCRIPTION A convenience wrapper over Get-SnowRecord for the cmdb_ci table. .PARAMETER Query An encoded query, for example 'sys_class_name=cmdb_ci_linux_server'. .PARAMETER Fields The fields to return. .PARAMETER Limit The maximum number of CIs to return (default 100). .PARAMETER All Returns every matching CI. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE Get-SnowCI -Query 'install_status=1^sys_class_name=cmdb_ci_linux_server' -Fields name,ip_address,os_version -All Lists all installed Linux servers with their IP address and OS version. .EXAMPLE Get-SnowCI -Query 'operational_status=2' -Fields name,sys_class_name | Export-SnowRecord -Format Csv -Path .\non-operational-cis.csv Exports non-operational CIs for an audit. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Query, [string[]]$Fields, [ValidateRange(1, 10000)][int]$Limit = 100, [switch]$All, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $request = @{ Table = 'cmdb_ci'; Query = $Query; Fields = $Fields; Limit = $Limit; All = $All } if ($Backend) { $request.Backend = $Backend } Get-SnowRecord @request } function Get-SnowCIRelationship { <# .SYNOPSIS Gets CMDB configuration item relationships. .DESCRIPTION A convenience wrapper over Get-SnowRecord for the cmdb_rel_ci table. Parent and child values are sys_ids of CIs. .PARAMETER Query An encoded query such as 'parent=<sys_id>' or 'child=<sys_id>'. .PARAMETER Fields The fields to return. .PARAMETER Limit The maximum number of relationships to return (default 100). .PARAMETER All Returns every matching relationship. .PARAMETER Backend Rest, Sdk or Auto. .EXAMPLE $ci = Get-SnowCI -Query 'name=prod-web-01' -Fields sys_id | Select-Object -First 1 Get-SnowCIRelationship -Query "child=$($ci.sys_id)" -All Finds what depends on a server before a maintenance window. .EXAMPLE Get-SnowCIRelationship -Query "parent=$($ci.sys_id)" -Fields type,child | Format-Table Lists the downstream items of a CI. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Query, [string[]]$Fields, [ValidateRange(1, 10000)][int]$Limit = 100, [switch]$All, [ValidateSet('Rest', 'Sdk', 'Auto')][string]$Backend ) $request = @{ Table = 'cmdb_rel_ci'; Query = $Query; Fields = $Fields; Limit = $Limit; All = $All } if ($Backend) { $request.Backend = $Backend } Get-SnowRecord @request } function Export-SnowRecord { <# .SYNOPSIS Exports records as CSV or JSON. .DESCRIPTION Accepts pipeline objects from the Snowplow cmdlets. With -Path the output is written to a file; without it the text is returned on the pipeline. .PARAMETER InputObject The records to export. Normally supplied from the pipeline. .PARAMETER Format Csv (default) or Json. .PARAMETER Path The file to write. Omit to return text. .EXAMPLE Get-SnowRecord -Table incident -Query 'active=true' -Fields number,short_description,priority -All | Export-SnowRecord -Format Csv -Path .\open-incidents.csv Produces a spreadsheet-friendly report of open incidents. .EXAMPLE Get-SnowCI -Limit 10 | Export-SnowRecord -Format Json Writes JSON to the pipeline, for example to feed another tool. .EXAMPLE Get-SnowRecord -Table sys_properties -Query 'nameSTARTSWITHglide.ui' -All | Export-SnowRecord -Format Json -Path .\ui-properties-backup.json Takes a backup of system properties before changing them. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)][psobject]$InputObject, [ValidateSet('Csv', 'Json')][string]$Format = 'Json', [string]$Path ) begin { $items = [System.Collections.Generic.List[object]]::new() } process { $items.Add($InputObject) } end { $text = if ($Format -eq 'Csv') { (@($items) | ConvertTo-Csv -NoTypeInformation) -join [Environment]::NewLine } else { ConvertTo-Json -InputObject @($items) -Depth 100 } if ($Path) { Set-Content -LiteralPath $Path -Value $text -Encoding utf8NoBOM } else { $text } } } function Compare-SnowRecord { <# .SYNOPSIS Compares two record objects and returns the differing properties. .DESCRIPTION Returns changed, added and missing properties. Records can be collected from the same instance or from separate instances; connect to each in turn and keep the results in variables. .PARAMETER Reference The baseline record. .PARAMETER Difference The record to compare against the baseline. .PARAMETER Property Limits the comparison to these properties. .EXAMPLE Connect-Snowplow -Profile dev -ClientCredentials $dev = Get-SnowRecord -Table sys_properties -Query 'name=glide.smtp.host' Connect-Snowplow -Profile prod -ClientCredentials $prod = Get-SnowRecord -Table sys_properties -Query 'name=glide.smtp.host' Compare-SnowRecord -Reference $dev -Difference $prod -Property value Checks a system property for drift between Dev and Prod. .EXAMPLE Compare-SnowRecord -Reference $before -Difference $after Shows exactly what a script changed on a record. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)][psobject]$Reference, [Parameter(Mandatory)][psobject]$Difference, [string[]]$Property ) $names = if ($Property) { $Property } else { @($Reference.PSObject.Properties.Name) + @($Difference.PSObject.Properties.Name) | Sort-Object -Unique } foreach ($name in $names) { $leftProperty = $Reference.PSObject.Properties[$name] $rightProperty = $Difference.PSObject.Properties[$name] $left = if ($leftProperty) { $leftProperty.Value } else { $null } $right = if ($rightProperty) { $rightProperty.Value } else { $null } if (-not $leftProperty -or -not $rightProperty -or (ConvertTo-Json -InputObject $left -Depth 50 -Compress) -cne (ConvertTo-Json -InputObject $right -Depth 50 -Compress)) { [pscustomobject]@{ Property = $name; Reference = $left; Difference = $right } } } } |