Public/New-ServiceNowQuery.ps1
<#
.SYNOPSIS Build query string for api call .DESCRIPTION Build query string for api call, there are basic and advanced methods; see the different parameter sets. Basic allows you to look for exact matches as well as fields that are like a value; these are all and'd together. You can also sort your results, ascending or descending, by 1 field. Advanced allows you to perform the (almost) complete set of operations that ServiceNow has. The comparison operators have been made to mimic powershell itself so the code should be easy to understand. You can use a very large set of comparison operators (see the script variable ServiceNowOperator), and, or, and grouping joins, as well as multiple sorting parameters. .PARAMETER Filter Array or multidimensional array of fields and values to filter on. Each array should be of the format @(field, comparison operator, value) separated by a join, either 'and', 'or', or 'group'. For a complete list of comparison operators, see $script:ServiceNowOperator and use Name in your filter. See the examples. Also, see https://docs.servicenow.com/bundle/quebec-platform-user-interface/page/use/common-ui-elements/reference/r_OpAvailableFiltersQueries.html for how to represent dates. .PARAMETER Sort Array or multidimensional array of fields to sort on. Each array should be of the format @(field, asc/desc). .EXAMPLE New-ServiceNowQuery -MatchExact @{field_name=value} Get query string where field name exactly matches the value .EXAMPLE New-ServiceNowQuery -MatchContains @{field_name=value} Get query string where field name contains the value .EXAMPLE New-ServiceNowQuery -Filter @('state', '-eq', '1'), 'or', @('short_description','-like', 'powershell') Get query string where state equals New or short description contains the word powershell .EXAMPLE $filter = @('state', '-eq', '1'), 'and', @('short_description','-like', 'powershell'), 'group', @('state', '-eq', '2') PS > New-ServiceNowQuery -Filter $filter Get query string where state equals New and short description contains the word powershell or state equals In Progress. The first 2 filters are combined and then or'd against the last. .EXAMPLE New-ServiceNowQuery -Filter @('state', '-eq', '1') -Sort @('opened_at', 'desc'), @('state') Get query string where state equals New and first sort by the field opened_at descending and then sort by the field state ascending .EXAMPLE New-ServiceNowQuery -Filter @('opened_at', '-ge', 'javascript:gs.daysAgoEnd(30)') Get query string where the record was opened in the last 30 days .INPUTS None .OUTPUTS String #> function New-ServiceNowQuery { [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', 'No state is actually changing')] [CmdletBinding()] [OutputType([System.String])] param( # Machine name of the field to order by [parameter(ParameterSetName = 'Basic')] [string] $OrderBy = 'opened_at', # Direction of ordering (Desc/Asc) [parameter(ParameterSetName = 'Basic')] [ValidateSet("Desc", "Asc")] [string] $OrderDirection = 'Desc', # Hashtable containing machine field names and values returned must match exactly (will be combined with AND) [parameter(ParameterSetName = 'Basic')] [hashtable] $MatchExact, # Hashtable containing machine field names and values returned rows must contain (will be combined with AND) [parameter(ParameterSetName = 'Basic')] [hashtable] $MatchContains, [parameter(ParameterSetName = 'Advanced')] [System.Collections.ArrayList] $Filter, [parameter(ParameterSetName = 'Advanced')] [System.Collections.ArrayList] $Sort ) Write-Verbose ('{0} - {1}' -f $MyInvocation.MyCommand, $PSCmdlet.ParameterSetName) if ( $PSCmdlet.ParameterSetName -eq 'Advanced' ) { if ( $Filter ) { $filterList = $Filter # see if we're working with 1 array or multidimensional array # we want multidimensional so convert if not if ($Filter[0].GetType().Name -eq 'String') { $filterList = @(, $Filter) } $query = for ($i = 0; $i -lt $filterList.Count; $i++) { $thisFilter = $filterList[$i] # allow passing of string instead of array # useful for joins if ($thisFilter.GetType().Name -eq 'String') { $thisFilter = @(, $thisFilter) } switch ($thisFilter.Count) { 0 { # nothing to see here Continue } 1 { # should be a join switch ($thisFilter[0]) { { $_ -in 'and', '-and' } { '^' } { $_ -in 'or', '-or' } { '^OR' } { $_ -in 'group', '-group' } { '^NQ' } Default { throw "Unsupported join operator '$($thisFilter[0])'. 'and', 'or', and 'group' are supported." } } # make sure we don't end on a join if ( $i -eq $filterList.Count - 1) { throw '$Filter cannot end with a join' } break } { $_ -ne 1 } { # perform data validation on all filters other than a join operator $thisOperator = $script:ServiceNowOperator | Where-Object { $_.Name -eq $thisFilter[1] } if ( -not $thisOperator ) { throw ('Operator ''{0}'' is not valid' -f $thisFilter[1]) } if ( $thisOperator.NumValues -ne $thisFilter.Count - 2 ) { throw ('Operator ''{0}'' requires 1 field name and {1} value(s)' -f $thisFilter[1], $thisOperator.NumValues) } } 2 { # should be a non-value operator, eg. ='' / ISEMPTY '{0}{1}' -f $thisFilter[0], $thisOperator.QueryOperator break } 3 { # should be format - field operator value if ( $thisFilter[2] -is [DateTime] ) { $dateGen = "'{0}','{1}'" -f $thisFilter[2].ToString('yyyy-MM-dd'), $thisFilter[2].ToString('HH:mm:ss') '{0}{1}javascript:gs.dateGenerate({2})' -f $thisFilter[0], $thisOperator.QueryOperator, $dateGen } else { '{0}{1}{2}' -f $thisFilter[0], $thisOperator.QueryOperator, $thisFilter[2] } break } 4 { # should be format - field operator value1 value2, where applicable, eg. between if ( $thisFilter[2] -is [DateTime] ) { $dateGen1 = "'{0}','{1}'" -f $thisFilter[2].ToString('yyyy-MM-dd'), $thisFilter[2].ToString('HH:mm:ss') $dateGen2 = "'{0}','{1}'" -f $thisFilter[3].ToString('yyyy-MM-dd'), $thisFilter[3].ToString('HH:mm:ss') '{0}{1}javascript:gs.dateGenerate({2})@javascript:gs.dateGenerate({3})' -f $thisFilter[0], $thisOperator.QueryOperator, $dateGen1, $dateGen2 } else { '{0}{1}{2}@{3}' -f $thisFilter[0], $thisOperator.QueryOperator, $thisFilter[2], $thisFilter[3] } break } Default { throw ('Too many filter items for {0}, see the help' -f $thisFilter[0]) } } } } # force query to an array in case we only got one item and its a string # otherwise below add to query won't work as expected $query = @($query) if ($query) { $query += '^' } $orderList = $Sort if ( $Sort ) { # see if we're working with 1 array or multidimensional array # we want multidimensional so convert if not if ($Sort[0].GetType().Name -eq 'String') { $orderList = @(, $Sort) } } $query += for ($i = 0; $i -lt $orderList.Count; $i++) { $thisOrder = $orderList[$i] if ( $orderList.Count -gt 1 -and $i -gt 0 ) { '^' } switch ($thisOrder.Count) { 0 { # nothing to see here Continue } 1 { # should be field, default to ascending 'ORDERBY' $thisOrder[0] } 2 { switch ($thisOrder[1]) { 'asc' { 'ORDERBY' } 'desc' { 'ORDERBYDESC' } Default { throw "Invalid order direction '$_'. Provide either 'asc' or 'desc'." } } $thisOrder[0] } Default { throw ('Too many items for {0}, see the help' -f $thisOrder[0]) } } } ($query -join '').Trim('^') } else { # Basic parameter set # Create StringBuilder $Query = New-Object System.Text.StringBuilder # Start the query off with a order direction $direction = Switch ($OrderDirection) { 'Asc' { 'ORDERBY'; break } Default { 'ORDERBYDESC' } } [void]$Query.Append($direction) # Add OrderBy [void]$Query.Append($OrderBy) # Build the exact matches into the query If ($MatchExact) { ForEach ($Field in $MatchExact.keys) { $ExactString = "^{0}={1}" -f $Field.ToString().ToLower(), ($MatchExact.$Field) [void]$Query.Append($ExactString) } } # Add the values which given fields should contain If ($MatchContains) { ForEach ($Field in $MatchContains.keys) { $ContainsString = "^{0}LIKE{1}" -f $Field.ToString().ToLower(), ($MatchContains.$Field) [void]$Query.Append($ContainsString) } } # Output StringBuilder to string $Query.ToString() } } |