#################### TicketWhere functions - BEGIN #################### #region Function Find-CoreTicket { <# .SYNOPSIS Function for searching for tickets in CORE via CTKAPI .DESCRIPTION Function for doing a ticket based search via the Core API (CTKAPI) You can specify multiple search criteria to focus your search You essentially need to choose your search terms and the output. Output is set by the params Attributes and CustomAttributes. You can ignore these and a default set of ticket attributes will be returned, like subject and number. If you need more information from your search, add attribute groups with the attribute param. You can always add custom attributes if you want to get even more granular See the help for the attribute param for this function for more information Input is set by the other params, simply select them and fill in the criteria In the background this function is using the where statement on the ticket class against the CTKAPI and parsing the output. It will also: - Manage your token, prompting for new one if it expires - Parse output into a Powershell friendly output - If the query is too large it will automatically start breaking it out into smaller ones - Has throttling protection to stop CTKAPI Make your search as specific as possible, so use as many function parameters as you can to limit your search. It will be faster and you will get far more useful data back. Dont just search for all tickets on an account and filter in powershell, its SLOW! There are many, many variations of search, returning lots of data. Check examples section for some inspiration as well as the param help for details on the search types Input for a single search criteria is limited to 249. This is a CTKAPI limitation. If you want to search for more then 249 of a single thing, say, 500 computers, you will need to split the input up before you pass it to this function as any input will not accept more then 249 Try to limit the number of attributes you return, in particular the groups "Computers","Messages","Subtickets". These are actually subattributes, which can be really slow as it is puts more work on the CTKAPI so avoid using these unless you absolutly have too! It is best practice to limit the attribute groups you use as well. While pretty small, all of these will have an effect on the time a query takes See the help for Get-CoreTicketAttributes and the attribute param for this function for more information. Make sure you check the CustomAttribute help as well, as this may be useful for limiting your querys Throttling will stop you performing too many queries in a time frame, so if you see a pause during a query, this is to stop the CTKAPI rejecting your query .PARAMETER State Optional String The state of the ticket, select Open or All If param is not provided, it will default to Open Open is anything in an awake statue, All is all tickets without specifiying a state. Core states: https://ws.core.rackspace.com/ctkapi/browse/Ticket/StatusType .PARAMETER AssigneeSsos Optional SSOs for users assigned to the ticket, can be array.(Max 249) If you do not know SSOs you can use Find-CoreContact or Get-Racker in the DevelopmentTools module If you want a single user, just pass the single username Validated against regex Example: mart6771 Example: george.smith Example: @("mart6771","george.smith") .PARAMETER Accounts Optional Integer Account numbers that the tickets will be under, can be array. (Max 249) If you want a single account, just pass the single account number Validated against regex Example: 1103359 Example: 965448 Example: @(1103359,965448) .PARAMETER Computers Optional Integer Device numbers that are attached to a ticket. Can be array. (Max 249) If you want a single device, just pass the single device number Validated against regex Example: 560559 Example: @(560559,560570) .PARAMETER Queues Optional Integer Queue numbers to look for tickets in, can be array. (Max 249) If you want a single queue, just pass the single queue number Queue numbers are listed here: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Queue Validated against regex Example: 52 Example: @(52,501) .PARAMETER Tickets Optional String Tickets numbers to restrict search by, can be array. (Max 249) If you want a single ticket, just pass the single ticket number (make sure its a string else powershell will do math on it => 123456-63254 = 60202) Queue numbers are listed here: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Queue Validated against regex Example: "161206-12345" Example: @("161206-12345","161206-12333") .PARAMETER Subjects Optional String Ticket subjects to search for, can be array. (Max 249) They will automatically be wild card searched both before and after, so "E2E" will become "*E2E*" If you want a single subject, just pass the single subject IF you dont want a wild card search, you are out of luck as i have not added this option yet Validated against regex. Must have 3 alphanumeric character Example: "baa" Example: @("baa","this is a ticket subject") .PARAMETER CreatedBefore Optional Only get tickets created before this date Must be date/time object .PARAMETER CreatedAfter Optional Only get tickets created after this date Must be date/time object .PARAMETER HasWindowsComputers Optional Only return tickets that have a Windows computer attached .PARAMETER Attributes Specify groups of ticket attributes to return with your search There is a default list of attributes that will always be returned, but you can specify additional groups For example, if you wanted Category information on ticket "161125-03022", you would do this: $Result = Find-CoreTicket -Tickets "161125-03022" -Attributes Category $Result.Result segment_queue : Intensive (All Teams) name : 560559-DB1.rackspace.com status_name : Online/Complete number : 560559 contract : 87732 datacenter_symbol : LON3 nickname : ttt account_name : International Enterprise Support Test Account account_id : 1103359 Use the following command to return all ticket attributes and their group names Get-CoreTicketAttributes -PrintAttributes Be cautious using the groups "Computers","Messages","Subtickets". These are actually subattributes, which can be slow, so avoid using these unless you absolutly have too You can also use the CustomAttributes param to return individual attributes See the help for Get-CoreTicketAttributes for more information .PARAMETER CustomAttributes Hashtable of additional attributes to return with your search There is a default list of ticket attributes that will always be returned You can specify additional groups using the Attribute parameter (see Attribute param help) You can also get more attributes here: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Ticket If you want to just return 1 or 2 additional params, use this parameter. You will need to provide a hashtable of attributes, where the friendly name is the key and the value is the Core API search For example, if you wanted to return the subcategory name but didnt want to use the Category attribute group, you could use this: $Result = Find-CoreTicket -Tickets "161125-03022" -CustomAttribute @{"subcategory_name"="subcategory.name"} $result.result.subcategory_name You can see that PoshCore converts the core api value (subcategory.nameid) into the friendly value You can return more then 1 custom value, just supply them in the same hashtable: $Result = Find-CoreTicket -Tickets "161125-03022" -CustomAttribute @{"subcategory_name"="subcategory.name" ; "account_name"="account.name"} Returns: subcategory_name : Windows - Other queue_name : INTL Windows number : 161125-03022 private : 0 assignee_name : Martin Howlett queue_id : 683 submitter : Martin Howlett account_name : International Enterprise Support Test Account subject : Test ticket You are encouraged to use the attribute groups first before turning to custom attributes, as its simpler You can use the command Get-CoreTicketAttributes -PrintAttributes to get a list of attributes and their CustomAttribute syntax .PARAMETER FullObject Optional Switch Returns the full object from core (.result) plus the time taken by the query,load args,limit,offset,count of objects retruned,etc If switch is not specified the .result content are only returned i.e. the data from core which is the default. Example of retruning Full object count : 19 load_arg : @{limit=100; values=System.Object[]; class=Ticket.TicketWhere; offset=100} limit : 100 result : {@{Computers=System.Object[]; queue_name=Intensive (All Teams); number=170209-07819; private=....} installed/loaded}, @{Computers=System.Object[]; queue_name=Intensive (All Teams); number=1702....} queue_name=Contract Processing; number=170213-02900; private=1; assignee_name=; queue_id=13;....} number=170213-02903; private=0; assignee_name=Cameron McAnsh; queue_id=684; submitter=System....} offset : 100 class : Ticket.Ticket QueryTime : 0.4495868 .PARAMETER QueryLimit The number of results to return before breaking up the query into smaller chunks Useful for avoiding timeouts Defaults to 100, max 249 Higher number likely to increase chance of timeout .EXAMPLE Get a list of tickets with windows device attached in the implementation queue with the specified subject $ImpTickets = Find-CoreTicket -HasWindowsComputers -Queues 510 -Subjects @("Provisioning-Master","Implementation Tracking Ticket") -Attributes Subtickets .EXAMPLE Get Computers on a ticket $TicketComputers = Find-CoreTicket -Tickets "161201-08495" -Attributes Computers $TicketComputers.Computers .EXAMPLE Get an open tickets subtickets $TicketSubticket = Find-CoreTicket -State Open -Tickets "161201-08495" -Attributes Subtickets $TicketComputers.Subtickets .EXAMPLE Get an All tickets specified no matter if there open or closed and returns there subtickets $TicketSubticket = Find-CoreTicket -State All -Tickets "161201-08495" -Attributes Subtickets $TicketComputers.Subtickets .EXAMPLE Get ticket on account and returns default attributes plus customattribute state_name $Tickets = Find-CoreTicket -Accounts 1103359 -CustomAttributes @{"status_name"="status.name"} .OUTPUTS The result object from Core .NOTES Written by Martin Howlett / Mark Wichall #> [CmdletBinding()] Param ( [Parameter()] [Validateset("Open","All")] [ValidateNotNullOrEmpty()] [String]$State, [Parameter()] [ValidatePattern("^[!\#\$%&'-\.0-9@A-Z\^_``a-z\{\}~\u00C0-\u024F]{1,64}$")] #https://stackoverflow.com/questions/20690499/concrete-javascript-regex-for-accented-characters-diacritics [ValidateCount(1,249)] [String[]]$AssigneeSsos, [Parameter()] [ValidatePattern("^[0-9]{4,9}$")] [ValidateNotNullOrEmpty()] [ValidateCount(1,249)] [Int[]]$Accounts, [Parameter()] [ValidatePattern("^[0-9]{6}$")] [ValidateNotNullOrEmpty()] [ValidateCount(1,249)] [Int[]]$Computers, [Parameter()] [ValidatePattern("^\d{1,4}$")] [ValidateNotNullOrEmpty()] [ValidateCount(1,249)] [Int[]]$Queues, [Parameter()] [ValidatePattern("^\d{6}-\d{5}$")] [ValidateCount(1,249)] [String[]]$Tickets, [Parameter()] [ValidatePattern("\w")] [ValidateCount(1,249)] [String[]]$Subjects, [Parameter()] [ValidateNotNullOrEmpty()] [datetime]$CreatedBefore, [Parameter()] [ValidateNotNullOrEmpty()] [datetime]$CreatedAfter, [Parameter()] [ValidateNotNullOrEmpty()] [Switch]$HasWindowsComputers, [ValidateRange(1,249)] [Parameter()] [int]$QueryLimit = 100, [Parameter()] [Validateset("Account","Time","Has","Is","Category","Misc","Computers","Messages","Subtickets","Superticket")] [ValidateNotNullOrEmpty()] [String[]]$Attributes, [Parameter()] [ValidateNotNullOrEmpty()] [Hashtable]$CustomAttributes, [ValidateNotNullOrEmpty()] [Switch]$FullObject ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #Create the dictionary that will hold our query arguments [Hashtable]$SearchTerms = @{} Write-Verbose $SearchTerms.GetType() Write-Verbose "State set to: $State" #Warning if state not set and default to 'open' If ($State -eq $null -or $state -eq "") { $State = "Open" Write-Warning "NOTE: No State parameter specified defaulting to 'Open'" Write-Warning "Searches will not return terminal tickets, specify parameter state 'All' to retrun all tickets" } #Add state #set our ticket state search option. We always add this to reduce the noise from a search Switch ($State) { "Open" { $SearchTerms.Add("Status", [Array]("status_type","in", [Array](8))) break } "All" { <# Note: State-type is not set for all tickets as setting all of the status types returns no results. Not defining status type returns all results. Specifying a state of closed e.g. 3 & 6, fails to return closed tickets due to the way it works in the API and it referencing different tables, as a result we removed this functionality in version 1.0.0.1 #> break } Default { #Default to Open Status. $SearchTerms.Add("Status", [Array]("status_type","in", [Array](8))) } } #Add assigned SSOs if($AssigneeSsos) { #Make array unique $AssigneeSsos = Get-UniqueArray -InputArray $AssigneeSsos $SearchTerms.Add("AssigneeSsos",[Array]("current_assignee_sso","in", [System.Collections.ArrayList]$AssigneeSsos)) Write-Verbose "Added AssigneeSsos: $($AssigneeSsos -join ",")" } #Add account numbers if($Accounts) { #Make array unique $Accounts = Get-UniqueArray -InputArray $Accounts #Add to our hashtable $SearchTerms.Add("Accounts",[Array]("account","in", [System.Collections.ArrayList]$Accounts)) Write-Verbose "Added Accounts: $($Accounts -join ",")" } #Add device list if($Computers) { #Make array unique $Computers = Get-UniqueArray -InputArray $Computers $SearchTerms.Add("Computers",[Array]("computer","in", [System.Collections.ArrayList]$Computers)) Write-Verbose "Added Computers: $($Computers -join ",")" } #Add queue list if($Queues) { #Make array unique $Queues = Get-UniqueArray -InputArray $Queues $SearchTerms.Add("Queues",[Array]("queue","in", [System.Collections.ArrayList]$Queues)) Write-Verbose "Added Queues: $($Queues -join ",")" } #Add ticket list if($Tickets) { #Make array unique $Tickets = Get-UniqueArray -InputArray $Tickets $SearchTerms.Add("Tickets",[Array]("number","in", [System.Collections.ArrayList]$Tickets)) Write-Verbose "Added Tickets: $($Tickets -join ",")" } #Add wildcard subject search if($Subjects) { #Make array unique $Subjects = Get-UniqueArray -InputArray $Subjects Write-Verbose "Added Subjects: $($Subjects -join ",")" <# if we have more then one entry, the JSON is formated in such a way where we need an array of OR statements Powershell loves to merge arrays so we create a new arraylist and add all the subject lines to this new list. We can use the subject property multiple times in one query. this also works with just one subject #> #create new array object for our subjects [System.Collections.ArrayList]$SubjectOBject = @() #Subject count $SubjectCount = 0 #iterate through our subjects and add them to a new array list ForEach ($line in $Subjects) { $SubjectOBject.Add( [Array]([Array]("subject","like","%$line%")) ) | Out-Null $SubjectCount ++ #only add the OR operator if we are not at the end of the array if($SubjectCount -ne $Subjects.count) { $SubjectOBject.Add("|") | Out-Null } } $SearchTerms.Add("Subjects",$SubjectOBject) } #Add Created Before criteria if($CreatedBefore) { $SearchTerms.Add("CreatedBefore", [Array]("created","<=","$($CreatedBefore.ToString("yyyy-MM-dd HH:mm:ss"))") ) Write-Verbose "Added CreatedBefore: $($CreatedBefore)" } #Add Created After criteria if($CreatedAfter) { $SearchTerms.Add("CreatedAfter", [Array]("created",">=","$($CreatedAfter.ToString("yyyy-MM-dd HH:mm:ss"))") ) Write-Verbose "Added CreatedAfter: $($CreatedAfter)" } #add windows server criteria if($HasWindowsComputers) { $SearchTerms.Add("HasWindowsComputers", [Array]("has_windows_servers","=",$True) ) Write-Verbose "Added HasWindowsComputers" } #if we dont have any search terms, throw an error if($SearchTerms.keys.count -eq 0) { Throw "No criteria selected for ticket search" } #Add our list of user selected attributes [Hashtable]$CoreTicketAttributes = @{} if($Attributes) { $CoreTicketAttributes.Add("Attributes",$Attributes) } $QueryAttributes = Get-CoreTicketAttributes @CoreTicketAttributes #Build our variables for the Core where statement handler [Hashtable]$WrapperParams = @{ SearchTerms = $SearchTerms Attributes = $QueryAttributes Class = "Ticket" QueryLimit = $QueryLimit } #if the user has supplied a custom attribute hashtable, we pass this to our where wrapper if($CustomAttributes) { $WrapperParams.Add("CustomAttributes",$CustomAttributes) } $result = Invoke-CoreWhereWrapper @WrapperParams #returns the .result value as default, full object returned if $FullObject switch used. If (-not $FullObject) { $result = $result.result } return $result } End { #Log to appstats Set-LogtoAppstats -FunctionID "Find-CoreTicket" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function Get-CoreTicketAttributes { <# .SYNOPSIS Returns attributes for ticketwhere queries .DESCRIPTION Creates a hashtable of attributes used with ticketwhere statements in Core Using the attribute switch you can build up set of attributes by specify the groups you want included To get an idea of what attributes are in what group, run the command: Get-CoreTicketAttributes -PrintAttributes This will print all the groups to the screen to find the one you need The default group of attributes will always be returned and cannot be changed You can specify additional attributes using the custom attribute param. Use the printattribute command to see the syntax The reason we return a hash table is so we can get the Core API to return friendly names so we do not have to process stuff For example this entry: @{"segment"="segment.name"} Typically the Core API would return a Segment Class, so by using dot notation we have Core expand it to return the name. By using a hashtable in this fashion, the "friendly" name is the key and the actual Core query is the value ------------ DEV NOTES: ------------- If you are adding or removing an attribute group, you must make sure you: - Update the ValidateSet for attributes on Find-CoreTicket - Update the ValidateSet for attributes on this function - Update the array for attributes in the $PrintAttributes if statement at the bottom of this function If you are adding an attribute group, you MUST make sure the hashtable variable you create matches this naming convention: $TicketAttributes so for example, Computers would be $ComputersTicketAttributes. This is the same name used in the ValidateSet. if you name the variable differently it will not be picked up by the loop in the PrintAttributes section so will not be written to screen If you are adding a subattribute group, make sure you link it to the default attribute list of the other classes function For example, for subattribute Computers, i get the default computer params from Get-CoreComputerAttributes $ComputersTicketAttributes = Get-CoreComputerAttributes [Hashtable]$ComputersubAttributes = @{ "Computers" = @{ "id" = "number" "attribute" = "computers" "subattributes" = $ComputersTicketAttributes } } .PARAMETER Attributes Optional Array of additional groups of attribute to return This would typically be passed from Find-CoreTicket You can specify any combination of attribute groups you want, depending on your requirements Returning every group is obviously going to slow down a core query, so try to keep the groups to a minimum The groups "Computers","Messages","Subtickets" are actually subattributes, which will be even slower, so avoid using these unless you absolutly have too To see a list of what attributes are in what group, use Get-CoreTicketAttributes -PrintAttributes .PARAMETER PrintAttributes Can only be used by itself Run to get a print out of all the attribute groups and their PoshCore name, Core API value and the Custom attribute syntax Example output of default values for ticket (note, values subject to change) Attribute group name: Default ---------------------------------------------------------------------------------------------------------------------- | PoshCore name: | Core API value: | Custom attribute syntax: | ---------------------------------------------------------------------------------------------------------------------- | queue_id | queue.id | @{"queue_id"="queue.id"} | | number | number | @{"number"="number"} | | subject | subject | @{"subject"="subject"} | | submitter | submitter.name | @{"submitter"="submitter.name"} | | queue_name | queue.name | @{"queue_name"="queue.name"} | | assignee_name | assignee.name | @{"assignee_name"="assignee.name"} | | private | private | @{"private"="private"} | ---------------------------------------------------------------------------------------------------------------------- .EXAMPLE Prints out of all the attribute groups and their PoshCore name, Core API value and the Custom attribute syntax Get-CoreTicketAttributes -PrintAttributes .EXAMPLE Get a hashtable of the default ticket attributes. This should then be passed to Get-CoreWhereWrapper attribute param $Attributes = Get-CoreTicketAttributes .EXAMPLE Get a hashtable of the default, time and category related attributes. This should then be passed to Get-CoreWhereWrapper attribute param $Attributes = Get-CoreTicketAttributes -Attributes Time,Category output hashtable: Name Value ---- ----- number number difficulty difficulty last_public_response_date last_public_response_date classification_name classification.name priority priority.name subcategory_name subcategory.name last_response_date last_response_date private private modified modified subject subject assignee_name assignee.name created created queue_name queue.name age age idle idle severity_name severity.name category_name category.name submitter submitter.name queue_id queue.id .EXAMPLE Get a hashtable of the default, Computers, Subtickets and Account related attributes. This should then be passed to Get-CoreWhereWrapper attribute param $Attributes = Get-CoreTicketAttributes -Attributes Computers,Subtickets,Account Note that because Subtickets and Computers are subattributes, the value is another hashtable output hashtable: Name Value ---- ----- Computers {id, attribute, subattributes} assignee_name assignee.name account_number account.number submitter submitter.name private private number number account_name account.name subject subject queue_id queue.id subtickets {number, attribute, subattributes} queue_name queue.name .OUTPUTS Hashtable of attributes to use with Invoke-CoreQueryWrapper .NOTES Written by Martin Howlett #> [CmdletBinding(DefaultParameterSetName='Attributes')] Param ( [Parameter(ParameterSetName="Attributes")] [Validateset("Default","Account","Time","Has","Is","Category","Misc","Computers","Messages","Subtickets","Superticket")] [AllowEmptyString()] [String[]]$Attributes = "Default", [Parameter(ParameterSetName="PrintAttributes")] [Switch]$PrintAttributes ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #The default list of params we will always return [Hashtable]$DefaultTicketAttributes = @{ "number" = "number" "subject" = "subject" "private" = "private" "queue_name" = "queue.name" "queue_id" = "queue.id" "assignee_name" = "assignee.name" "submitter" = "submitter.name" } [Hashtable]$AccountTicketAttributes = @{ "account_name" = "account.name" "account_number" = "account.number" } [Hashtable]$TimeTicketAttributes = @{ "age" = "age" "idle" = "idle" "modified" = "modified" "created" = "created" "last_public_response_date" = "last_public_response_date" "last_response_date" = "last_response_date" } [Hashtable]$HasTicketAttributes = @{ "has_first_public_response" = "has_first_public_response" "has_customer_response" = "has_customer_response" "has_linux_servers" = "has_linux_servers" "has_public_comment" = "has_public_comment" "has_uk_am" = "has_uk_am" "has_open_subtickets" = "has_open_subtickets" "has_windows_servers" = "has_windows_servers" } [Hashtable]$IsTicketAttributes = @{ "is_closeable" = "is_closeable" "is_closed" = "is_closed" "is_internal" = "is_internal" "is_scheduled_service" = "is_scheduled_service" "is_solved" = "is_solved" "is_subticket" = "is_subticket" "is_superticket" = "is_superticket" "is_terminal" = "is_terminal" } [Hashtable]$CategoryTicketAttributes = @{ "priority" = "priority.name" "difficulty" = "difficulty" "category_name" = "category.name" "severity_name" = "severity.name" "subcategory_name" = "subcategory.name" "classification_name" = "classification.name" } [Hashtable]$MiscTicketAttributes = @{ "source_name" = "source.name" "status_name" = "status.name" "transaction_nps" = "transaction_nps" "superticket_number" = "superticket.number" "support_level" = "support_level" "support_team" = "support_team.name" "revenue_mrr_amount" = "revenue.mrr_amount" "work_log" = "work_log" } #the subattributes for getting Computers attached to a ticket #notice we also use Get-CoreComputerAttributes to return the default device attribute list #we save it to $ComputersTicketAttributes so it will get picked up when we use the -PrintAttribute param $ComputersTicketAttributes = Get-CoreComputerAttributes [Hashtable]$ComputersubAttributes = @{ "Computers" = @{ "id" = "number" "attribute" = "computers" "subattributes" = $ComputersTicketAttributes } } #The subattribute params for getting the content of ticket messages #we have hardcoded the values to return as they do not apply anywhere else #we save it to $MessagesTicketAttributes so it will get picked up when we use the -PrintAttribute param [Hashtable]$MessagesTicketAttributes = @{ "text" = "text" "is_visible" = "is_visible" "is_customer" = "is_customer" "date" = "date" "private" = "private" "contact" = "contact.name" "source" = "source.name" } [Hashtable]$MessageSubAttributes = @{ "messages" = @{ "id" = "id" "attribute" = "messages" "subattributes" = $MessagesTicketAttributes } } #the subattributes for getting tickets attached to a ticket #notice we also use $DefaultTicketAttributes to return the default Ticket attribute list for subtickets #we save it to $SubticketsTicketAttributes so it will get picked up when we use the -PrintAttribute param $SubticketsTicketAttributes = $DefaultTicketAttributes [Hashtable]$SubticketSubAttributes = @{ "subtickets" = @{ "number" = "number" "attribute" = "subtickets" "subattributes" = $SubticketsTicketAttributes } } #As for subtickets, so superticket. Only difference is that you'll never get more than one. $SuperticketTicketAttributes = $DefaultTicketAttributes [Hashtable]$SuperticketSubAttributes = @{ "superticket" = @{ "number" = "number" "attribute" = "superticket" "subattributes" = $SuperticketTicketAttributes } } #Create output object [Hashtable]$TicketAttributes = @{} #Add our top level attributes $TicketAttributes += $DefaultTicketAttributes Write-Verbose "adding Default attributes" Switch ($Attributes) { {$_ -contains 'Account'}{$TicketAttributes += $AccountTicketAttributes ; Write-Verbose "adding Account attributes"} {$_ -contains 'Time'}{$TicketAttributes += $TimeTicketAttributes ; Write-Verbose "adding Time attributes"} {$_ -contains 'Has'}{$TicketAttributes += $HasTicketAttributes ; Write-Verbose "adding Has attributes"} {$_ -contains 'Is'}{$TicketAttributes += $IsTicketAttributes ; Write-Verbose "adding Is attributes"} {$_ -contains 'Category'}{$TicketAttributes += $CategoryTicketAttributes ; Write-Verbose "adding Category attributes"} {$_ -contains 'Misc'}{$TicketAttributes += $MiscTicketAttributes ; Write-Verbose "adding Misc attributes"} {$_ -contains 'Computers' }{$TicketAttributes += $ComputersubAttributes ; Write-Verbose "adding Computers attributes"} {$_ -contains 'Messages' }{$TicketAttributes += $MessageSubAttributes ; Write-Verbose "adding Messages attributes"} {$_ -contains 'Subtickets'}{$TicketAttributes += $SubticketSubAttributes ; Write-Verbose "adding Subtickets attributes"} {$_ -contains 'Superticket'}{$TicketAttributes += $SuperticketSubAttributes ; Write-Verbose "adding Supertickets attributes"} } #if user selected the print attribute switch we just print them to screen and dont return anything if($PrintAttributes) { Write-Verbose "PrintAttributes selected" #set variables to generate our hash table for Write-AttributesToHost #$AllGroups should match the ValidateSet on the Attributes param on this function $AttributeClass = "Ticket" $AllGroups = @("Account","Time","Has","Is","Category","Misc","Computers","Messages","Subtickets","Superticket","Default") #create the hashtable of all attribute hashtable groups we pass to Write-Attributes $AttributeHashtable = @{} ForEach ($Group in $AllGroups) { $AttributeHashtable.Add($Group,$((Get-Variable -Name "$Group$AttributeClass`Attributes").Value)) } Write-AttributesToHost -AttributeHashtable $AttributeHashtable } else { Return $TicketAttributes } } End { #Log to appstats Set-LogtoAppstats -FunctionID "Get-CoreTicketAttributes" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } #endregion #################### TicketWhere functions - END ##################### #################### QueueWhere functions - BEGIN #################### #region Function Find-CoreQueue { <# .SYNOPSIS Function for finding Ticket Queues via Core API (CTKAPI) .DESCRIPTION Function for finding Ticket Queues via Core API (CTKAPI) You can specify a queue name or ID to get its description, name and ID back .PARAMETER QueueNames Optional string Array or single string The name of the queue to search for (Max 249) .PARAMETER QueueIds Optional Integer Array or single Integer The ID of the queue to search for (Max 249) .PARAMETER QueryLimit The number of results to return before breaking up the query into smaller chunks Useful for avoiding timeouts Defaults to 100, max 249 Higher number likely to increase chance of timeout .PARAMETER CustomAttributes Hashtable of additional attributes to return with your search There is a default list of ticket attributes that will always be returned You can specify additional groups using the Attribute parameter (see Attribute param help) You can also get more attributes here: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Queue If you want to just return 1 or 2 additional params, use this parameter. You will need to provide a hashtable of attributes, where the friendly name is the key and the value is the Core API search .PARAMETER FullObject Optional Switch Returns the full object from core (.result) plus the time taken by the query,load args,limit,offset,count of objects returned,etc If switch is not specified the .result content are only returned i.e. the data from core which is the default. Example of returning Full object count : 19 load_arg : @{limit=100; values=System.Object[]; class=Ticket.TicketWhere; offset=100} limit : 100 result : {@{Computers=System.Object[]; queue_name=Intensive (All Teams); number=170209-07819; private=....} installed/loaded}, @{Computers=System.Object[]; queue_name=Intensive (All Teams); number=1702....} queue_name=Contract Processing; number=170213-02900; private=1; assignee_name=; queue_id=13;....} number=170213-02903; private=0; assignee_name=Cameron McAnsh; queue_id=684; submitter=System....} offset : 100 class : Ticket.Ticket QueryTime : 0.4495868 .EXAMPLE Find the ID of the INTL Windows queue Find-CoreQueue -QueueNames "INTL Windows" .EXAMPLE Find the name of queue ID 683 Find-CoreQueue -QueueIds "683" .NOTES Written by Martin Howlett / Mark Wichall #> [CmdletBinding()] Param ( [Parameter()] [ValidatePattern("\w")] [ValidateNotNullOrEmpty()] [ValidateCount(1,249)] [String[]]$QueueNames, [Parameter()] [ValidatePattern("^\d{1,4}$")] [ValidateCount(1,249)] [Int[]]$QueueIds, [Parameter()] [ValidateRange(1,249)] [int]$QueryLimit = 100, [Parameter()] [ValidateNotNullOrEmpty()] [Hashtable]$CustomAttributes, [Parameter()] [ValidateNotNullOrEmpty()] [Switch]$FullObject ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #Create the dictionary that will hold our query arguments [Hashtable]$SearchTerms = @{} Write-Verbose $SearchTerms.GetType() #Add queue names if($QueueNames) { #Make array unique $QueueNames = Get-UniqueArray -InputArray $QueueNames $SearchTerms.Add("QueueNames",[Array]("name","in", [System.Collections.ArrayList]$QueueNames)) Write-Verbose "Added AssigneeSsos: $($QueueNames -join ",")" } if($QueueIds) { #Make array unique $QueueIds = Get-UniqueArray -InputArray $QueueIds $SearchTerms.Add("QueueIds",[Array]("id","in", [System.Collections.ArrayList]$QueueIds)) Write-Verbose "Added AssigneeSsos: $($QueueIds -join ",")" } #if we dont have any search terms, throw an error if($SearchTerms.keys.count -eq 0) { Throw "No criteria selected for queue search" } #Hardcode our attributes as its such a simple search $QueryAttributes = @{ "id"= "id" "name"= "name" "description" = "description" } #Build our variables for the Core where statement handler [Hashtable]$WrapperParams = @{ SearchTerms = $SearchTerms Attributes = $QueryAttributes Class = "Ticket" Subclass = "Queue" QueryLimit = $QueryLimit } #if the user has supplied a custom attribute hashtable, we pass this to our where wrapper if($CustomAttributes) { $WrapperParams.Add("CustomAttributes",$CustomAttributes) } $result = Invoke-CoreWhereWrapper @WrapperParams #Returns the .result value as default, full object returned if $FullObject switch used. If (-not $FullObject) { $result = $result.result } return $result } End { #Log to appstats Set-LogtoAppstats -FunctionID "Find-CoreQueue" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } #endregion #################### QueueWhere functions - END ##################### #################### Ticket method wrapper functions - BEGIN #################### #region Function Find-CoreTicketQueueCategory { <# .SYNOPSIS Returns categories and subcategories for a queue .DESCRIPTION Returns categories and subcategories for a queue These are context sensitive to a queue in CTKAPI, so you need to know the queue ID to get the categories. You can get a queue ID using the below command Find-CoreQueue" -QueueNames "INTL Windows" Function can also search for a particular subcategory ID or name, useful for getting the ID for functions and vice versa This function is used in New-CoreTicket to get the subcategory ID from a queue to use with the new-coreticket method which only accepts ID .PARAMETER QueueId Mandatory The ID of the queue you want the catergories for USe this to get a queue ID from a name: Find-CoreQueue" -QueueNames "INTL Windows" .PARAMETER QueueId Mandatory The ID of the queue you want the catergories for USe this to get a queue ID from a name: Find-CoreQueue" -QueueNames "INTL Windows" .PARAMETER ReturnAll Cannot be used with SearchId or SearchName Returns all categories and subcategories for the queue ID selected .PARAMETER SearchId Cannot be used with ReturnAll Can be used with SearchName Searches the IDs on the returned subcategory ID and returns matching name Returns false if not found .PARAMETER SearchName Cannot be used with ReturnAll Can be used with SearchId Searches the name on the returned subcategory names and returns matching ID Returns false if not found .EXAMPLE Get all catergory and subcatergories for INTL Windows Find-CoreTicketQueueCategory -QueueId 683 -ReturnAll .EXAMPLE Get the subcatergory ID for "Upgrade - Software" on the INTL Windows queue Find-CoreTicketQueueCategory -QueueId 683 -SearchName "Upgrade - Software" Returns: CategoryId : 5370 SubcategoryName : Upgrade - Software SubcategoryDesc : Upgrade - Software CategoryDesc : Additional Services - Upgrades CategoryName : Additional Services - Upgrades SubcategoryId : 35427 .EXAMPLE Get the name and category for subcategory ID 35352 Find-CoreTicketQueueCategory -QueueId 683 -SearchID 35352 Returns: CategoryId : 5366 SubcategoryName : Windows - Performance Analysis SubcategoryDesc : Windows - Performance Analysis CategoryDesc : Operating Services CategoryName : Operating Services SubcategoryId : 35352 .OUTPUTS Psobject or ArrayCollection of Catergory/Subcategory info .NOTES Written by Martin Howlett #> [CmdletBinding(DefaultParameterSetName="Search")] Param ( [Parameter(ParameterSetName="ReturnAll")] [Parameter(ParameterSetName="Search")] [Parameter(Mandatory=$true)] [ValidatePattern("^\d{1,4}$")] [ValidateNotNullOrEmpty()] [Int]$QueueId, [Parameter(ParameterSetName="ReturnAll")] [ValidateNotNullOrEmpty()] [Switch]$ReturnAll, [Parameter(ParameterSetName="Search")] [Parameter()] [ValidateNotNullOrEmpty()] [Int]$SearchId, [Parameter(ParameterSetName="Search")] [Parameter()] [ValidateNotNullOrEmpty()] [String]$SearchName ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #get the values from CTKAPI $CoreMethodParams = @{ LoadArgument = $QueueId KeywordArgs = @{} Method = "getSubcategories" Class = "Ticket" Subclass = "Queue" } $QueryResult = Invoke-CoreMethodWrapper @CoreMethodParams #Expand the subcatagories out into 1 level object [Array]$Catergories = $null foreach ($MainCategory in $QueryResult.result) { foreach ($Subcategory in $MainCategory.subcategories) { $Temp = New-Object PSObject -Property @{ CategoryName = $MainCategory.name CategoryId = $MainCategory.id CategoryDesc = $MainCategory.description SubcategoryName = $Subcategory.name SubcategoryId = $Subcategory.id SubcategoryDesc = $Subcategory.description } $Catergories += $Temp } } #switch depending on params used Switch ($PSCmdlet.ParameterSetName) { "Search" { #search for subcategory based in params use if($SearchId -and $SearchName) { $SearchResult = $Catergories | Where-Object {($_.SubcategoryName -eq "$SearchName") -and ($_.SubcategoryId -eq "$SearchId")} } elseif($SearchId) { $SearchResult = $Catergories | Where-Object {$_.SubcategoryId -like $SearchId} } elseif($SearchName) { $SearchResult = $Catergories | Where-Object {$_.SubcategoryName -like $SearchName} } else { Throw "Find-CoreTicketQueueCategory - You must provide a search ID or search name if you do not select the ReturnAll switch" } #return result or false if not found if($SearchResult) { Return $SearchResult } else { Return $false } } "ReturnAll" { #just return everything Return $Catergories } } } End { #Log to appstats Set-LogtoAppstats -FunctionID "Find-CoreTicketQueueCategory" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function New-CoreTicket { <# .SYNOPSIS Create a new ticket via CTKAPI .DESCRIPTION Takes a set of input params and will use them to create a new Core ticket under an account There are complex input params, so please pay attention to the individual param help .PARAMETER AccountId Mandatory Account number the ticket will be under .PARAMETER Subject Mandatory The subject of the ticket .PARAMETER Text Mandatory The text of the first ticket update BBCode is supported If you want it to be private, use the switch -PrivateTicket .PARAMETER QueueId QueueId or QueueName is mandatory. The queue the ticket will be placed in QueueId is prefered as it is less queries to Core Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER QueueName QueueId or QueueName is mandatory. The queue the ticket will be placed in QueueId is prefered as it is less queries to Core This param is useful when you don't know the Queue ID and using the fuction in a Powershell session It is less efficient then QueueId as it has to resolve the Queue Id each time you call the function, so its slower If you are writing a script, dont use QueueName, use QueueId! Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER SubcategoryId SubcategoryId or SubcategoryName is mandatory. The Subcategory the ticket will be assigned The category is automatically applied based on Subcategory. Subcategories are queue specific SubcategoryId is prefered as it is less queries to Core Use this to get the Subcategory ID, where SearchName is the Subcategory name: Find-CoreTicketQueueCategory -QueueId 683 -SearchName "Upgrade - Software" Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER SubcategoryName SubcategoryId or SubcategoryName is mandatory. The Subcategory the ticket will be assigned The category is automatically applied based on Subcategory. Subcategories are queue specific SubcategoryId is prefered as it is less queries to Core This param is useful when you don't know the Subcategory ID and using the fuction in a Powershell session It is less efficient then SubcategoryId as it has to resolve the Subcategory Id each time you call the function, so its slower If you are writing a script, dont use SubcategoryName, use SubcategoryId! Use this to get the Subcategory ID, where SearchName is the Subcategory name: Find-CoreTicketQueueCategory -QueueId 683 -SearchName "Upgrade - Software" Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER Source Optional The source of the ticket update Use the ValidateSet to pick a value Default to "CORE Automated" More info: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Source .PARAMETER Severity Optional The Severity of the ticket Use the ValidateSet to pick a value Default to "Normal" More info: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Severity .PARAMETER Classification Optional The Classification of the ticket Use the ValidateSet to pick a value More info: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Classification .PARAMETER Computers Optional Array of computer IDs to attach to the ticket on creation Can be a single computer ID Must be under the same account Can be up to 100 computer IDs (arbitrary limit to avoid killing CTKAPI) .PARAMETER AssigneeSsoId AssigneeSsoId or AssigneeSsoName are optional. The Core contact ID of the user to assign the Core ticket to AssigneeSsoId is prefered as it is less queries to Core Use this to get the Contact ID using a SSO: $Contact = Find-CoreContact -Ssos "mart6141" -IsEmployee $Contact.ID .PARAMETER AssigneeSsoName AssigneeSsoId or AssigneeSsoName are optional. The SSO username of the user to assign the new Core ticket AssigneeSsoId is prefered as it is less queries to Core This param is useful when you don't know the contact ID of the SSO of the user and using the fuction in a Powershell session It is less efficient then AssigneeSsoId as it has to resolve the contact Id each time you call the function, so its slower If you are writing a script, dont use AssigneeSsoName, use AssigneeSsoId! Use this to get the Contact ID using a SSO: $Contact = Find-CoreContact -Ssos "mart6141" -IsEmployee $Contact.ID .PARAMETER PrivateTicket Optional Switch to define a ticket as private If not used ticket will be public .EXAMPLE Basic ticket using the minimum params, function returns the ticket id created $Params = @{ AccountId = 1103359 Subject = "Test ticket" Text = "Test ticket!" QueueId = 683 SubcategoryId = 35354 ] New-CoreTicket @Params .EXAMPLE Basic ticket using the inefficient QueueName, SubcategoryName and AssigneeSsoName params $Params = @{ AccountId = 1103359 Subject = "Test ticket" Text = "This is my test ticket!" QueueName = "INTL Windows" SubcategoryName = "Windows - Other" Priority = "Low" AssigneeSsoName = "mart6141" PrivateTicket = $true } New-CoreTicket @Params .EXAMPLE Basic ticket using the efficient QueueId and SubcategoryID params $Params = @{ AccountId = 1103359 Subject = "Test ticket" Text = "This is my test ticket!" QueueID = 683 SubcategoryID = 35354 AssigneeSsoName = "mark4808" PrivateTicket = $true } New-CoreTicket @Params .EXAMPLE Complex example using all parameters. $Params = @{ AccountId = 1103359 Subject = "Test ticket" Text = "Test ticket!" QueueId = 683 SubcategoryId = 35354 Source = "CORE Automated" Severity = "Standard" Priority = "Low" Classification = "Request for Change" Computers = @(560559,560570) AssigneeSsoName = "mark4808" PrivateTicket = $true } New-CoreTicket @Params .EXAMPLE Complex example using queue name. $Params = @{ AccountId = 1103359 Subject = "Windows ADC" Text = "Windows ADC Ticket generated by LastMile" QueueName = "Provisioning UK - OS" SubcategoryId = 35354 Source = "CORE Automated" Severity = "Standard" Priority = "Low" Classification = "Request for Change" Computers = @(560559,560570) AssigneeSsoName = "mark4808" PrivateTicket = $true } New-CoreTicket @Params .OUTPUTS Ticket number .NOTES Written by Martin Howlett #> Param ( [Parameter(Mandatory=$true)] [ValidatePattern("^[0-9]{4,9}$")] [ValidateNotNullOrEmpty()] [Int]$AccountId, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String]$Subject, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String]$Text, #QueueID or QueueName required. Enforced in function [Parameter()] [ValidatePattern("^\d{1,4}$")] [ValidateNotNullOrEmpty()] [Int]$QueueId, [Parameter()] [ValidatePattern("\w")] [ValidateNotNullOrEmpty()] [String]$QueueName, #SubcategoryName or SubcategoryId required. Enforced in function [Parameter()] [ValidateNotNullOrEmpty()] [ValidatePattern("\w")] [String]$SubcategoryName, [Parameter()] [ValidatePattern("^\d{2,8}$")] [Int]$SubcategoryId, [Parameter()] [Validateset("Phone Call","MyRackspace","Rackspace Employee","CORE Automated","Genie","Mass Ticket","Walk-Up","EZCASH","Email","Other","Chat Support","RunBook","RackerApp")] [ValidateNotNullOrEmpty()] [String]$Source = "CORE Automated", [Parameter()] [Validateset("Standard","Urgent","Emergency")] [ValidateNotNullOrEmpty()] [String]$Severity = "Standard", [Parameter()] [Validateset("Low","Normal","High","Highest")] [ValidateNotNullOrEmpty()] [String]$Priority = "Normal", [Parameter()] [Validateset("NONE","Request for Information","Request for Change","Incident","Customer Feedback","Request for Service","Problem","Capacity","Major Incident")] [ValidateNotNullOrEmpty()] [String]$Classification, [Parameter()] [ValidatePattern("^[0-9]{6}$")] [ValidateNotNullOrEmpty()] [ValidateCount(1,100)] [Int[]]$Computers, #AssigneeSsoId or AssigneeSsoName is optional, dont use both [Parameter()] [ValidatePattern("^\d{4,10}$")] [Int]$AssigneeSsoId, [Parameter()] [ValidatePattern("^[!\#\$%&'-\.0-9@A-Z\^_``a-z\{\}~\u00C0-\u024F]{1,64}$")] #https://stackoverflow.com/questions/20690499/concrete-javascript-regex-for-accented-characters-diacritics [String]$AssigneeSsoName, [Parameter()] [Switch]$PrivateTicket ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #################### Queue name - BEGIN #################### #region if(-not ($QueueId -or $QueueName)) { Throw "PoshCore - New-CoreTicket - You must supply a Queue name or Queue ID" } if($QueueName) { #Convert our QueueName into a QueueID Write-Verbose "QueueName parameter supplied ($QueueName)" #find the queue attrbute values $Queue = $null $Queue = Find-CoreQueue -QueueNames "$QueueName" if(-not $Queue.ID) { Throw "PoshCore - New-CoreTicket - Could not find a match for queue name $QueueName" } [int]$QueueId = $Queue.ID Write-Verbose "Found queue ID $QueueID from $QueueName" Write-Warning "For best performance you should use Queue ID and not Queue name. Queue name is good when you are not sure on ID. Use this parameter -> -QueueId $QueueId" } #endregion #################### Queue name - END ##################### #################### Subcategory name - BEGIN #################### #region if(-not ($SubcategoryId -or $SubcategoryName)) { Throw "PoshCore - New-CoreTicket - You must supply a Subcategory name or Subcategory ID" } if($SubcategoryName) { Write-Verbose "SubcategoryName parameter supplied ($SubcategoryName)" #resolve our catergory name and get the ID back $Category = $null $Category = Find-CoreTicketQueueCategory -QueueId $QueueId -SearchName $SubcategoryName if(-not $Category.SubcategoryId) { Throw "PoshCore - New-CoreTicket - Could not find a match for subcategory name $SubcategoryName" } [Int]$SubcategoryId = $Category.SubcategoryId Write-Verbose "Found Subcategory ID $SubcategoryId from $SubcategoryName" Write-Warning "For best performance you should use subcategory ID and not subcategory name. Subcategory name is good when you are not sure on ID. Use this parameter -> -SubcategoryId $SubcategoryId" } #endregion #################### Subcategory name - END ##################### #################### Assignee Sso Name - BEGIN #################### #region #this param is not required, if not supplied we simply create the ticket unassigned if($AssigneeSsoName) { Write-Verbose "AssigneeSsoName parameter supplied ($AssigneeSsoName)" #search sso for Core contact ID $Contact = Find-CoreContact -Ssos $AssigneeSsoName -IsEmployee if(-not $Contact.employee_userid) { Throw "PoshCore - New-CoreTicket - Could not find a match for SSO name $AssigneeSsoName" } $AssigneeSsoId = $Contact.id Write-Verbose "Found contactID $AssigneeSsoId from $AssigneeSsoName" Write-Warning "For best performance you should use Assignee SSO ID and not Assignee SSO name. Assignee SSO name is good when you are not sure on ID. Use this parameter -> -AssigneeSsoId $AssigneeSsoId" } #endregion #################### Assignee Sso Name - END ##################### #################### Convert friendly names to IDs - BEGIN #################### #region #Convert our friendly params to CTKAPI params which uses ID numbers Switch ($Severity) { "Standard" {$SeverityId = 1} "Urgent" {$SeverityId = 2} "Emergency" {$SeverityId = 3} } Switch ($Priority) { "Low" {$PriorityId = 2} "Normal" {$PriorityId = 1} "High" {$PriorityId = 3} "Highest" {$PriorityId = 4} } Switch ($Source) { "Phone Call" {$SourceId = 1} "MyRackspace" {$SourceId = 2} "Rackspace Employee" {$SourceId = 3} "CORE Automated" {$SourceId = 4} "Genie" {$SourceId = 5} "Mass Ticket" {$SourceId = 6} "Walk-Up" {$SourceId = 7} "EZCASH" {$SourceId = 8} "Email" {$SourceId = 9} "Other" {$SourceId = 10} "Chat Support" {$SourceId = 11} "RunBook" {$SourceId = 12} "RackerApp" {$SourceId = 13} } Switch ($Classification) { "NONE" {$ClassificationId = "1"} "Request for Information" {$ClassificationId = "2"} "Request for Change" {$ClassificationId = "3"} "Incident" {$ClassificationId = "4"} "Customer Feedback" {$ClassificationId = "5"} "Request for Service" {$ClassificationId = "6"} "Problem" {$ClassificationId = "7"} "Capacity" {$ClassificationId = "8"} "Major Incident" {$ClassificationId = "9"} } #endregion #################### Convert friendly names to IDs - BEGIN #################### #CRLF to LF $Text = $Text -replace "`r`n","`n" #Build requirement params #bbcode is not a requirement but it makes sense to include it always $KeyWordArgs = @{ queue = $QueueId subcategory = $SubcategoryId source = $SourceId severity = $SeverityId subject = $Subject text = $Text has_bbcode = $True } #Add optional params if supplied if($Computers) { #make sure its a unique list of computer numbers! $Computers = Get-UniqueArray $Computers $KeyWordArgs.Add("computer_list",[Array]$Computers) } if($PrivateTicket) { $KeyWordArgs.Add("is_private_ticket",$true) } if($AssigneeSsoId) { $KeyWordArgs.Add("assignee",$AssigneeSsoId) } if($PriorityId) { $KeyWordArgs.Add("priority",$PriorityId) } if($ClassificationId) { $KeyWordArgs.Add("classification",$ClassificationId) } #post to core $Result = Invoke-CoreMethodWrapper -LoadArgument $AccountId -KeywordArgs $KeyWordArgs -Method "addTicket" -Class "Account" #Core object returns a boolean under success, if it works return the ticket number, or throw if($result.success) { Return $Result.result.load_value } else { $Reason = "Poshcore - New-CoreTicket - Failed to create CORE ticket" $Params = @{ FunctionName = "$($MyInvocation.MyCommand)" CallingScript = "$($MyInvocation.ScriptName)" Line = "$($_.invocationInfo.ScriptLineNumber)" ErrorMsg = "$($_.exception.message)" CommandName = "$($_.exception.CommandName)" Variables = $(Get-Variable -Scope Local -ErrorAction SilentlyContinue) Version = "$version" Reason = "$Reason" } Export-PowershellError @Params Throw "$Reason" } } End { #Log to appstats Set-LogtoAppstats -FunctionID "New-CoreTicket" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function New-CoreSubticket { <# .SYNOPSIS Create a new subticket on an existing ticket via CTKAPI .DESCRIPTION Takes a set of input params and will use them to create a new Core ticket under an existing ticket There are complex input params, so please pay attention to the individual param help .PARAMETER SuperTicket Mandatory Specifies to create the ticket as a subticket of the specified superticket. .PARAMETER Subject Mandatory The subject of the ticket .PARAMETER Text Mandatory The text of the first ticket update BBCode is supported If you want it to be private, use the switch -PrivateTicket; by default, it will inherit visibility from the superticket .PARAMETER QueueId QueueId or QueueName is mandatory. The queue the ticket will be placed in QueueId is prefered as it is less queries to Core .PARAMETER QueueName QueueId or QueueName is mandatory. The queue the ticket will be placed in QueueId is prefered as it is less queries to Core This param is useful when you don't know the Queue ID and using the fuction in a Powershell session It is less efficient then QueueId as it has to resolve the Queue Id each time you call the function, so its slower If you are writing a script, dont use QueueName, use QueueId! Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER SubcategoryId SubcategoryId or SubcategoryName is mandatory. The Subcategory the ticket will be assigned The category is automatically applied based on Subcategory. Subcategories are queue specific SubcategoryId is prefered as it is less queries to Core Use this to get the Subcategory ID, where SearchName is the Subcategory name: Find-CoreTicketQueueCategory -QueueId 683 -SearchName "Upgrade - Software" Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER SubcategoryName SubcategoryId or SubcategoryName is mandatory. The Subcategory the ticket will be assigned The category is automatically applied based on Subcategory. Subcategories are queue specific SubcategoryId is prefered as it is less queries to Core This param is useful when you don't know the Subcategory ID and using the fuction in a Powershell session It is less efficient then SubcategoryId as it has to resolve the Subcategory Id each time you call the function, so its slower If you are writing a script, dont use SubcategoryName, use SubcategoryId! Use this to get the Subcategory ID, where SearchName is the Subcategory name: Find-CoreTicketQueueCategory -QueueId 683 -SearchName "Upgrade - Software" Use this to get the Queue ID, where INTL Windows is the queue name: Find-CoreQueue -QueueNames "INTL Windows" .PARAMETER Source Optional The source of the ticket update Use the ValidateSet to pick a value Default to "CORE Automated" More info: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Source .PARAMETER Severity Optional The Severity of the ticket Use the ValidateSet to pick a value Default to "Normal" More info: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Severity .PARAMETER Priority Optional The Priority of the ticket Use the ValidateSet to pick a value Default to "Standard" More info: https://ws.core.rackspace.com/ctkapi/browse/Ticket/Severity .PARAMETER Computers Optional Array of computer IDs to attach to the ticket on creation Can be a single computer ID Must be under the same account Can be up to 100 computer IDs (arbitrary limit to avoid killing CTKAPI) .PARAMETER AssigneeSsoId AssigneeSsoId or AssigneeSsoName are optional. The Core contact ID of the user to assign the Core ticket to AssigneeSsoId is prefered as it is less queries to Core Use this to get the Contact ID using a SSO: $Contact = Find-CoreContact -Ssos "mart6141" -IsEmployee $Contact.ID .PARAMETER AssigneeSsoName AssigneeSsoId or AssigneeSsoName are optional. The SSO username of the user to assign the new Core ticket AssigneeSsoId is prefered as it is less queries to Core This param is useful when you don't know the contact ID of the SSO of the user and using the fuction in a Powershell session It is less efficient then AssigneeSsoId as it has to resolve the contact Id each time you call the function, so its slower If you are writing a script, dont use AssigneeSsoName, use AssigneeSsoId! Use this to get the Contact ID using a SSO: $Contact = Find-CoreContact -Ssos "mart6141" -IsEmployee $Contact.ID .PARAMETER PrivateTicket Optional Switch to define a ticket as private If not used ticket will be public .EXAMPLE Basic ticket using the minimum params, function returns the ticket id created $Params = @{ Superticket = 180104-12345 Subject = "Test ticket" Text = "Test ticket!" QueueId = 683 } New-CoreSubticket @Params .EXAMPLE Basic ticket using the inefficient QueueName and AssigneeSsoName params $Params = @{ Superticket = 180104-12345 Subject = "Test ticket" Text = "This is my test ticket!" QueueName = "INTL Windows" Priority = "Low" AssigneeSsoName = "mart6141" PrivateTicket = $true } New-CoreSubticket @Params .EXAMPLE Basic ticket using the more efficient QueueId params $Params = @{ Superticket = 180104-12345 Subject = "Test ticket" Text = "This is my test ticket!" QueueID = 683 AssigneeSsoName = "mark4808" PrivateTicket = $true } New-CoreSubticket @Params .EXAMPLE Complex example using all parameters. $Params = @{ Superticket = 180104-12345 Subject = "Test ticket" Text = "Test ticket!" QueueId = 683 Source = "CORE Automated" Severity = "Standard" Priority = "Low" Computers = @(560559,560570) AssigneeSsoName = "mark4808" PrivateTicket = $true } New-CoreSubticket @Params .EXAMPLE Complex example using queue name. $Params = @{ Superticket = 180104-12345 Subject = "Windows ADC" Text = "Windows ADC Ticket generated by LastMile" QueueName = "Provisioning UK - OS" Source = "CORE Automated" Severity = "Standard" Priority = "Low" Computers = @(560559,560570) AssigneeSsoName = "mark4808" PrivateTicket = $true } New-CoreSubticket @Params .OUTPUTS Ticket number .NOTES Written by Freddie Sackur #> Param ( [Parameter(Mandatory = $true)] [ValidatePattern("^\d{6}-\d{5}$")] [String]$Superticket, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$Subject, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$Text, [Parameter()] [ValidatePattern("^\d{1,4}$")] [ValidateNotNullOrEmpty()] [Int]$QueueId, [Parameter()] [ValidatePattern("\w")] [ValidateNotNullOrEmpty()] [String]$QueueName, #SubcategoryName or SubcategoryId required. Enforced in function [Parameter()] [ValidateNotNullOrEmpty()] [ValidatePattern("\w")] [String]$SubcategoryName, [Parameter()] [ValidatePattern("^\d{2,8}$")] [Int]$SubcategoryId, [Parameter()] [Validateset("Phone Call","MyRackspace","Rackspace Employee","CORE Automated","Genie","Mass Ticket","Walk-Up","EZCASH","Email","Other","Chat Support","RunBook","RackerApp")] [ValidateNotNullOrEmpty()] [String]$Source = "CORE Automated", [Parameter()] [Validateset("Standard", "Urgent", "Emergency")] [ValidateNotNullOrEmpty()] [String]$Severity = "Standard", [Parameter()] [Validateset("Low", "Normal", "High", "Highest")] [ValidateNotNullOrEmpty()] [String]$Priority = "Normal", [Parameter()] [ValidatePattern("^[0-9]{6}$")] [ValidateNotNullOrEmpty()] [ValidateCount(1, 100)] [Int[]]$Computers, #AssigneeSsoId or AssigneeSsoName is optional, dont use both [Parameter()] [ValidatePattern("^\d{4,10}$")] [Int]$AssigneeSsoId, [Parameter()] [ValidatePattern("^[!\#\$%&'-\.0-9@A-Z\^_``a-z\{\}~\u00C0-\u024F]{1,64}$")] #https://stackoverflow.com/questions/20690499/concrete-javascript-regex-for-accented-characters-diacritics [String]$AssigneeSsoName, [Parameter()] [Switch]$PrivateTicket ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #################### Queue name - BEGIN #################### #region if($QueueName) { #Convert our QueueName into a QueueID Write-Verbose "QueueName parameter supplied ($QueueName)" #find the queue attrbute values $Queue = $null $Queue = Find-CoreQueue -QueueNames "$QueueName" if(-not $Queue.ID) { Throw ("{0} - {1} - Could not find a match for queue name {2}" -f $MyInvocation.MyCommand.Module, $MyInvocation.MyCommand.Name, $QueueId) } [int]$QueueId = $Queue.ID Write-Verbose "Found queue ID $QueueID from $QueueName" Write-Warning "For best performance you should use Queue ID and not Queue name. Queue name is good when you are not sure on ID. Use this parameter -> -QueueId $QueueId" } #endregion #################### Queue name - END ##################### #################### Subcategory name - BEGIN #################### #region if($SubcategoryName) { Write-Verbose "SubcategoryName parameter supplied ($SubcategoryName)" #resolve our catergory name and get the ID back $Category = $null if (-not $QueueId) { throw "If specifying subcategory by name, you must specify a queue" } $Category = Find-CoreTicketQueueCategory -QueueId $QueueId -SearchName $SubcategoryName if(-not $Category.SubcategoryId) { Throw ("{0} - {1} - Could not find a match for subcategory name {2}" -f $MyInvocation.MyCommand.Module, $MyInvocation.MyCommand.Name, $SubcategoryName) } [Int]$SubcategoryId = $Category.SubcategoryId Write-Verbose "Found Subcategory ID $SubcategoryId from $SubcategoryName" Write-Warning "For best performance you should use subcategory ID and not subcategory name. Subcategory name is good when you are not sure on ID. Use this parameter -> -SubcategoryId $SubcategoryId" } #endregion #################### Subcategory name - END ##################### #################### Assignee Sso Name - BEGIN #################### #region #this param is not required, if not supplied we simply create the ticket unassigned if($AssigneeSsoName) { Write-Verbose "AssigneeSsoName parameter supplied ($AssigneeSsoName)" #search sso for Core contact ID $Contact = Find-CoreContact -Ssos $AssigneeSsoName -IsEmployee if(-not $Contact.employee_userid) { Throw ("{0} - {1} - Could not find a match for assignee name {2}" -f $MyInvocation.MyCommand.Module, $MyInvocation.MyCommand.Name, $AssigneeSsoName) } $AssigneeSsoId = $Contact.id Write-Verbose "Found contactID $AssigneeSsoId from $AssigneeSsoName" Write-Warning "For best performance you should use Assignee SSO ID and not Assignee SSO name. Assignee SSO name is good when you are not sure on ID. Use this parameter -> -AssigneeSsoId $AssigneeSsoId" } #endregion #################### Assignee Sso Name - END ##################### #################### Convert friendly names to IDs - BEGIN #################### #region #Convert our friendly params to CTKAPI params which uses ID numbers Switch ($Severity) { "Standard" {$SeverityId = 1} "Urgent" {$SeverityId = 2} "Emergency" {$SeverityId = 3} } Switch ($Priority) { "Low" {$PriorityId = 2} "Normal" {$PriorityId = 1} "High" {$PriorityId = 3} "Highest" {$PriorityId = 4} } Switch ($Source) { "Phone Call" {$SourceId = 1} "MyRackspace" {$SourceId = 2} "Rackspace Employee" {$SourceId = 3} "CORE Automated" {$SourceId = 4} "Genie" {$SourceId = 5} "Mass Ticket" {$SourceId = 6} "Walk-Up" {$SourceId = 7} "EZCASH" {$SourceId = 8} "Email" {$SourceId = 9} "Other" {$SourceId = 10} "Chat Support" {$SourceId = 11} "RunBook" {$SourceId = 12} "RackerApp" {$SourceId = 13} } #endregion #################### Convert friendly names to IDs - BEGIN #################### #CRLF to LF $Text = $Text -replace "`r`n","`n" #Build requirement params #bbcode is not a requirement but it makes sense to include it always $KeyWordArgs = @{ subject = $Subject comment = $Text #comment, not text like in Account.addTicket() has_bbcode = $True computers = @() #required, even if empty array, unlike in Account.addTicket() } #Add optional params if supplied if ($QueueId) { $KeyWordArgs.Add("queue",$QueueId) } if ($SubcategoryId) { $KeyWordArgs.Add("subcategory",$SubcategoryId) } if ($SourceId) { $KeyWordArgs.Add("tk_source",$SourceId) #tk_source, not source like in Account.addTicket() } if ($SeverityId) { $KeyWordArgs.Add("severity",$SeverityId) } if($Computers) { #make sure its a unique list of computer numbers! $Computers = Get-UniqueArray $Computers $KeyWordArgs["computers"] = [Array]$Computers #computers, not computer_list like in Account.addTicket() } if($PrivateTicket) { $KeyWordArgs.Add("is_private_ticket",1) } if($AssigneeSsoId) { $KeyWordArgs.Add("assignee",$AssigneeSsoId) } if($PriorityId) { $KeyWordArgs.Add("priority",$PriorityId) } #post to core $Result = Invoke-CoreMethodWrapper -LoadArgument $Superticket -KeywordArgs $KeyWordArgs -Method "addSubTicket" -Class "Ticket" #Core object returns a boolean under success, if it works return the ticket number, or throw if($result.success) { Return $Result.result.load_value } else { $Reason = "Poshcore - New-CoreSubticket - Failed to create CORE ticket" $Params = @{ FunctionName = "$($MyInvocation.MyCommand)" CallingScript = "$($MyInvocation.ScriptName)" Line = "$($_.invocationInfo.ScriptLineNumber)" ErrorMsg = "$($_.exception.message)" CommandName = "$($_.exception.CommandName)" Variables = $(Get-Variable -Scope Local -ErrorAction SilentlyContinue) Version = "$version" Reason = "$Reason" } Export-PowershellError @Params Throw "$Reason" } } End { #Log to appstats Set-LogtoAppstats -FunctionID "New-CoreSubticket" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function Add-CoreTicketUpdate { <# .SYNOPSIS Adds a ticket update to a CORE ticket .DESCRIPTION Uses the CTKAPI to update a ticket with a new update .PARAMETER TicketNumber Mandatory The ticket number Must be a string else powershell will treat as equation .PARAMETER Text Mandatory The text of the update BBCode is always enabled .PARAMETER Source Optional The source of the ticket update Defaults to "Rackspace Employee" .PARAMETER PrivateUpdate Optional Switch Sets ticket update to private if selected Defaults to public (unless ticket is private) .EXAMPLE Basic ticket message update Add-CoreTicketUpdate -TicketNumber "170131-12345" -Test "This is a ticket update" .EXAMPLE Ticket message update with source Add-CoreTicketUpdate -TicketNumber "170131-12345" -Test "This is a ticket update" -Source "CORE Automated" .EXAMPLE Ticket message update with source and setting to private Add-CoreTicketUpdate -TicketNumber "170131-12345" -Test "This is a ticket update" -Source "CORE Automated" -PrivateUpdate .OUTPUTS True or false .NOTES Written by Martin Howlett #> Param ( [Parameter(Mandatory=$true)] [ValidatePattern("^\d{6}-\d{5}$")] [String]$TicketNumber, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String]$Text, [Parameter()] [Validateset("Phone Call","MyRackspace","Rackspace Employee","CORE Automated","Genie","Mass Ticket","Walk-Up","EZCASH","Email","Other","Chat Support","RunBook","RackerApp")] [ValidateNotNullOrEmpty()] [String]$Source = "Rackspace Employee", [Parameter()] [Switch]$PrivateUpdate ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #Convert our friendly params to CTKAPI params Switch ($Source) { "Phone Call" {$SourceId = 1} "MyRackspace" {$SourceId = 2} "Rackspace Employee" {$SourceId = 3} "CORE Automated" {$SourceId = 4} "Genie" {$SourceId = 5} "Mass Ticket" {$SourceId = 6} "Walk-Up" {$SourceId = 7} "EZCASH" {$SourceId = 8} "Email" {$SourceId = 9} "Other" {$SourceId = 10} "Chat Support" {$SourceId = 11} "RunBook" {$SourceId = 12} "RackerApp" {$SourceId = 13} } #Build requirement params #bbcode is not a requirement but it makes sense to include it #source is required but we default to "Rackspace Employee" (3) #CRLF to LF $Text = $Text -replace "`r`n","`n" $KeyWordArgs = @{ text = $Text source = $SourceId has_bbcode = $True } #Add optional params if($PrivateUpdate) { $KeyWordArgs.Add("private",1) } #post to Core $Result = Invoke-CoreMethodWrapper -LoadArgument $TicketNumber -KeywordArgs $KeyWordArgs -Method "addMessage" -Class Ticket #return result if($result.success) { Return $True } else { $Reason = "Poshcore - Add-CoreTicketUpdate - Failed to update core ticket" $Params = @{ FunctionName = "$($MyInvocation.MyCommand)" CallingScript = "$($MyInvocation.ScriptName)" Line = "$($_.invocationInfo.ScriptLineNumber)" ErrorMsg = "$($_.exception.message)" CommandName = "$($_.exception.CommandName)" Variables = $(Get-Variable -Scope Local -ErrorAction SilentlyContinue) Version = "$version" Reason = "$Reason" } Export-PowershellError @Params Throw "$Reason" } } End { #Log to appstats Set-LogtoAppstats -FunctionID "Add-CoreTicketUpdate" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function Set-CoreTicketStatus { <# .SYNOPSIS Set a status to a CORE ticket .DESCRIPTION Take the name of the status and change the ticket status .PARAMETER TicketNumber Mandatory The ticket number Must be a string else powershell will treat as equation .PARAMETER Status Mandatory The new status for the ticket .EXAMPLE Ticket set for Confirm Solved status Set-CoreTicketStatus -TicketNumber "170131-12345" -Status "Confirm Solved" .EXAMPLE Ticket set for In Progress status Set-CoreTicketStatus -TicketNumber "170131-12345" -Status "In Progress" .OUTPUTS True or false .NOTES Written by Martin Howlett Modified by Miguel Izaga #> Param ( [Parameter(Mandatory=$true)] [ValidatePattern("^\d{6}-\d{5}$")] [ValidateNotNullOrEmpty()] [String]$TicketNumber, [Parameter(Mandatory=$true)] [Validateset("New","Closed","Unsolved","REQUIRE Feedback","Request Feedback","Confirm Solved","Awaiting Response","In Progress")] [ValidateNotNullOrEmpty()] [String]$Status ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { $KeyWordArgs = @{ status_name = $Status } #post to Core $Result = Invoke-CoreMethodWrapper -LoadArgument $TicketNumber -KeywordArgs $KeyWordArgs -Method "setStatusByName" -Class Ticket #return result if($result.success) { Return $True } else { $Reason = "Poshcore - Set-CoreTicketStatus - Failed to update core ticket" $Params = @{ FunctionName = "$($MyInvocation.MyCommand)" CallingScript = "$($MyInvocation.ScriptName)" Line = "$($_.invocationInfo.ScriptLineNumber)" ErrorMsg = "$($_.exception.message)" CommandName = "$($_.exception.CommandName)" Variables = $(Get-Variable -Scope Local -ErrorAction SilentlyContinue) Version = "$version" Reason = "$Reason" } Export-PowershellError @Params Throw "$Reason" } } End { #Log to appstats Set-LogtoAppstats -FunctionID "Set-CoreTicketStatus" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function Add-CoreTicketComputer { <# .SYNOPSIS Adds device(s) to a CORE ticket .DESCRIPTION Uses the CTKAPI 'addComputer' method to add device(s) to a CORE ticket .PARAMETER TicketNumber Mandatory String The ticket number .PARAMETER Computers Mandatory Array of integers Computer numbers of the devices to be added to the ticket If you want a single computer, just pass the single computer number .PARAMETER PrivateUpdate Optional Switch Adds private ticket update with the added devices if selected .EXAMPLE Adding a single device to a ticket Add-CoreTicketComputer -TicketNumber "170905-07033" -Computers 502664 .EXAMPLE Adding multiple (array of) devices Add-CoreTicketComputer -TicketNumber "170905-07033" -Computers 502664,497379,660665,660664,678088 .EXAMPLE Adding private update to the ticket with the list of device(s) being added Add-CoreTicketComputer -TicketNumber "170905-07033" -Computers 502664,497379,660665,660664,678088 -PrivateUpdate .OUTPUTS True or false .NOTES Written by Dimitar Filipov #> Param ( [Parameter(Mandatory=$true)] [ValidatePattern("^\d{6}-\d{5}$")] [ValidateNotNullOrEmpty()] [String]$TicketNumber, [Parameter(Mandatory=$true)] [ValidatePattern("^[0-9]{6}$")] [ValidateNotNullOrEmpty()] [Int[]]$Computers, [Parameter()] [Switch]$PrivateUpdate ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #array to store all devices that have been successfully (or unsuccessfully) added to the ticket, it's used for the prive ticket update $SuccessfullyAddedDevices = @() $UnsuccessfullyAddedDevices = @() #Update the input array to remove any duplicate values, which will save time during execution $Computers = Get-UniqueArray -InputArray $Computers foreach ($Device in $Computers) { $KeyWordArgs = @{ computer = $Device } #post to Core $Result = Invoke-CoreMethodWrapper -LoadArgument $TicketNumber -KeywordArgs $KeyWordArgs -Method "addComputer" -Class Ticket #return result if($Result.success) { $SuccessfullyAddedDevices += $Device Write-Verbose "Successfully added device $Device to ticket $TicketNumber" } else { $UnsuccessfullyAddedDevices += $Device Write-Verbose "Failed to add device $Device to ticket $TicketNumber" } } #Add private ticket update if the parameter is specified if ($PrivateUpdate) { Write-Verbose "Adding private update to the ticket" $UpdateTicket = Add-CoreTicketUpdate -TicketNumber $TicketNumber -Text "Devices added to ticket: $($SuccessfullyAddedDevices -join ",")" -Source "CORE Automated" -PrivateUpdate } if (-NOT $UnsuccessfullyAddedDevices) { Return $True } else { $Reason = "Poshcore - Add-CoreTicketComputer - Failed to add device(s) $($UnsuccessfullyAddedDevices -join ",") to core ticket" $Params = @{ FunctionName = "$($MyInvocation.MyCommand)" CallingScript = "$($MyInvocation.ScriptName)" Line = "$($_.invocationInfo.ScriptLineNumber)" ErrorMsg = "$($_.exception.message)" CommandName = "$($_.exception.CommandName)" Variables = $(Get-Variable -Scope Local -ErrorAction SilentlyContinue) Version = "$Version" Reason = "$Reason" } Export-PowershellError @Params Throw "$Reason" } } End { #Log to Appstats Set-LogtoAppstats -FunctionID "Add-CoreTicketComputer" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } Function Remove-CoreTicketComputer { <# .SYNOPSIS Remove device(s) from a CORE ticket .DESCRIPTION Uses the CTKAPI 'removeComputer' method to remove device(s) from a CORE ticket .PARAMETER TicketNumber Mandatory String The ticket number .PARAMETER Computers Mandatory Array of integers Computer numbers of the devices to be removed from the ticket If you want a single computer, just pass the single computer number .PARAMETER PrivateUpdate Optional Switch Adds private ticket update with the removed devices if selected .EXAMPLE Removing a single device from a ticket Remove-CoreTicketComputer -TicketNumber "170905-07033" -Computers 502664 .EXAMPLE Removing multiple (array of) devices from a ticket Remove-CoreTicketComputer -TicketNumber "170905-07033" -Computers 502664,497379,660665,660664,678088 .EXAMPLE Adding private update to the ticket with the list of device(s) being removed Remove-CoreTicketComputer -TicketNumber "170905-07033" -Computers 502664,497379,660665,660664,678088 -PrivateUpdate .OUTPUTS True or false .NOTES Written by Dimitar Filipov #> Param ( [Parameter(Mandatory=$true)] [ValidatePattern("^\d{6}-\d{5}$")] [ValidateNotNullOrEmpty()] [String]$TicketNumber, [Parameter(Mandatory=$true)] [ValidatePattern("^[0-9]{6}$")] [ValidateNotNullOrEmpty()] [Int[]]$Computers, [Parameter()] [Switch]$PrivateUpdate ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #array to store all devices that have been successfully (or unsuccessfully) removed from the ticket, it's used for the prive ticket update $SuccessfullyRemovedDevices = @() $UnsuccessfullyRemovedDevices = @() #Update the input array to remove any duplicate values and prevent errors (e.g. can't remove same device twice) $Computers = Get-UniqueArray -InputArray $Computers foreach ($Device in $Computers) { $KeyWordArgs = @{ computer = $Device } #post to Core $Result = Invoke-CoreMethodWrapper -LoadArgument $TicketNumber -KeywordArgs $KeyWordArgs -Method "removeComputer" -Class Ticket #return result if($Result.success) { $SuccessfullyRemovedDevices += $Device Write-Verbose "Successfully removed device $Device from ticket $TicketNumber" } else { $UnsuccessfullyRemovedDevices += $Device Write-Verbose "Failed to remove device $Device from ticket $TicketNumber" } } #Add private ticket update if the parameter is specified if ($PrivateUpdate) { Write-Verbose "Adding private update to the ticket" $UpdateTicket = Add-CoreTicketUpdate -TicketNumber $TicketNumber -Text "Devices removed from ticket: $($SuccessfullyRemovedDevices -join ",")" -Source "CORE Automated" -PrivateUpdate } if (-NOT $UnsuccessfullyRemovedDevices) { Return $True } else { $Reason = "Poshcore - Remove-CoreTicketComputer - Failed to remove device(s) $($UnsuccessfullyRemovedDevices -join ",") to core ticket" $Params = @{ FunctionName = "$($MyInvocation.MyCommand)" CallingScript = "$($MyInvocation.ScriptName)" Line = "$($_.invocationInfo.ScriptLineNumber)" ErrorMsg = "$($_.exception.message)" CommandName = "$($_.exception.CommandName)" Variables = $(Get-Variable -Scope Local -ErrorAction SilentlyContinue) Version = "$Version" Reason = "$Reason" } Export-PowershellError @Params Throw "$Reason" } } End { #Log to Appstats Set-LogtoAppstats -FunctionID "Remove-CoreTicketComputer" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } function Close-CoreTicket { <# .SYNOPSIS Closes Core tickets in bulk. .DESCRIPTION This function allows you to close a number of tickets at a time - there is no upper limit. If an update message is supplied (which can be public or private) it will add that update. Ticket statuses are set to "Confirm Solved". .PARAMETER Tickets Mandatory String array A list of ticket IDs to close Example: 110518-12345 .PARAMETER Text String (multiline) The text to include in a ticket update Example: "Dear customer, this alert was generated by an issue with our monitoring system. It has been closed automatically." .PARAMETER PrivateUpdate Switch Whether the accompanying ticket update should be a private update .PARAMETER ClosureCodeProduct String Logs a closure code event under the specified product Example: "Misc" .PARAMETER ClosureCodeArea String Logs a closure code event under the specified area of the specified product Example: "DC Outage" .EXAMPLE Close-CoreTicket -Tickets "270418-03450", "270418-03451", "270418-03454" Sets three tickets to "Confirm Solved", without adding a comment .EXAMPLE $Tickets = Find-CoreTicket -Queues 683 -State Open -CreatedAfter (Get-Date).AddHours(-2) -Subjects "Test ticket" Close-CoreTicket -Tickets $Tickets.number -Text "Cleaning up test tickets" -PrivateUpdate Sets tickets to "Confirm Solved" with a private update in the user's name .EXAMPLE $Tickets = Find-CoreTicket -Queues 683 -Subjects "ALERT: IP unreachable" $MessageText = "Dear customer, this ticket is the result of a glitch in the matrix. We are now closing it." Close-CoreTicket -Tickets $Tickets.number -Text $MessageText -ClosureCodeProduct "Misc" -ClosureCodeArea "DC Outage" Sets matching tickets to "Confirm Solved" with a public update in the user's name and with the "DC Outage" closure code #> [CmdletBinding(DefaultParameterSetName = 'WithNoUpdate')] param ( [Parameter(Mandatory = $true, Position = 0)] [ValidatePattern('^\d{6}-\d{5}$')] [string[]]$Tickets, [Parameter(Mandatory = $true, ParameterSetName = 'WithUpdate')] [Parameter(Mandatory = $true, ParameterSetName = 'WithUpdateAndClosureCode')] [ValidateNotNullOrEmpty()] [String]$Text, [Parameter(ParameterSetName = 'WithUpdate')] [Parameter(ParameterSetName = 'WithUpdateAndClosureCode')] [Switch]$PrivateUpdate, [Parameter(Mandatory = $true, ParameterSetName = 'WithUpdateAndClosureCode')] [string]$ClosureCodeProduct, [Parameter(Mandatory = $true, ParameterSetName = 'WithUpdateAndClosureCode')] [string]$ClosureCodeArea ) $ClosureCodeSplat = @{ Product = $ClosureCodeProduct Area = $ClosureCodeArea Username = Get-SsoUsername iawScriptUsed = [switch]::Present SkipChecks = [switch]::Present RequestType = "Break Fix" } $PlaceholderText = "Closed by $(Get-SsoUsername) using $($MyInvocation.MyCommand.Name) in the $($MyInvocation.MyCommand.Module.Name) module" $i = 0 Write-Progress -Activity 'Closing tickets' -PercentComplete 0 #Significant rewrites would be needed to reduce the overhead of doing this one-by-one. #Each method in the Ticket class will only operate on one ticket at a time. #It is possible to send multiple method operations per API request, up to 250, but that #would require changes to Invoke-CoreMethodWrapper and Invoke-CoreQuery. foreach ($Ticket in $Tickets) { Write-Progress -Activity 'Closing tickets' -Status "Closing $Ticket" -PercentComplete (100 * $i / $Tickets.Count) $i += 1 if ($PSCmdlet.ParameterSetName -notmatch 'WithUpdate') { $ShouldClose = $true } else { #We should add a ticket update $CoreTicket = Find-CoreTicket -Tickets $Ticket -Attributes Messages -State Open if (-not $CoreTicket) { Write-Warning "Ticket $Ticket could not be found, or was closed already." continue } #Don't add ticket update to tickets that already have this update text, and the update is public if ($CoreTicket.messages | where {-not [bool][int]$_.private -and $_.Text -like $Text}) { $UpdateText = $PlaceholderText $PrivateUpdate = $true } else { $UpdateText = $Text $PrivateUpdate = $PSBoundParameters.PrivateUpdate -or $CoreTicket.private } $UpdateSplat = @{ TicketNumber = $Ticket Text = $UpdateText PrivateUpdate = $PrivateUpdate Source = 'Rackspace Employee' } $UpdateSuccess = Add-CoreTicketUpdate @UpdateSplat $ShouldClose = $UpdateSuccess } if ($ShouldClose) { $CloseSuccess = Set-CoreTicketStatus -TicketNumber $Ticket -Status 'Confirm Solved' if ($PSCmdlet.ParameterSetName -match 'ClosureCode') { Invoke-ClosureCodeRequest @ClosureCodeSplat -ticketNum $Ticket } } if (-not ($ShouldClose -and $CloseSuccess)) { Write-Error "Ticket $Ticket was not closed" } #Log to AppStats for each ticket Set-LogtoAppstats -FunctionID "Close-CoreTicket" -AppID "PoshCore" } Write-Progress -Activity 'Closing tickets' -Completed } #endregion #################### Ticket method wrapper functions - END ##################### #################### Ticket Update Functions - BEGIN #################### #region Function Set-CoreTicket { <# .SYNOPSIS Function for updating ticket properties in CORE via CTKAPI .DESCRIPTION Function allows you to specify multiple tickets and update multiple properties to update on those tickets Input is set by the other params, simply select them and fill in the value Input is validated and restricted as much as possible but please double check your inputs as you will be making changes to tickets SetAsigneeSso and SetAsigneeSsoId cannot be specified together as they update the same data e.g. the assign user SetQueueName and SetQueueID cannot be specified together as they update the same data e.g. the assign queue If you receive a 400 error check the values you are providing for each parameter, note SSO username is case sensitive. If you receive a 500 error it maybe that you don't have permissions for example if you try to move a ticket from a queue you don't have permissions to. When troubleshooting it's recommended that you test each individual param separately to rule out which paramter/item of data is causing the issue e.g. test with a single ticket (as it's mandatory) and one additional param Set-CoreTicket -Tickets "170726-03173" -SetDifficulty 1 In the background, this function is using the where wrapper on the ticket class against the CTKAPI and parsing the output. It will also: - Manage your token, prompting for new one if it expires - Parse output into a Powershell friendly output - If the query is too large it will automatically start breaking it out into smaller ones - Has throttling protection to stop CTKAPI Check examples section for some inspiration as well as the param help for details on what can be changed Throttling will stop you performing too many queries in a time frame, so if you see a pause during a query, this is to stop the CTKAPI rejecting your query .PARAMETER Tickets String Ticket numbers to update attributes on If you want a single ticket, just pass the single ticket number (make sure its a string else powershell will do math on it => 123456-63254 = 60202) Validated against regex Example: "161206-12345" Example: @("161206-12345","161206-12333") .PARAMETER SetAsigneeSso Optional String SSO to assign to the tickets If you do not know SSOs you can use Get-Racker in the DevelopmentTools module Validated against regex Example: mart6771 Example: george.smith .PARAMETER SetAsigneeSsoId Optional Integer Contact ID for user to assign to the ticket Example: 802884 .PARAMETER SetQueueName Optional String Queue Name to assign to the tickets If you know an existing ticket in the queue you can use Find-CoreTicket -Tickets and check queue_name Example: "INTL Windows" .PARAMETER SetQueueID Optional Integer Queue ID to assign to the tickets If you know an existing ticket in the queue you can use Find-CoreTicket -Tickets and check queue_id Example: 683 .PARAMETER SetDifficulty Optional Integer Ticket difficulty you want to set Example: 1 Example: 2 .PARAMETER FullObject Optional Switch Returns the full object from core (.result) plus the time taken by the query,load args,limit,offset,count of objects retruned,etc If switch is not specified the .result content are only returned i.e. the data from core which is the default. Example of retruning Full object count : 19 load_arg : @{limit=100; values=System.Object[]; class=Ticket.TicketWhere; offset=100} limit : 100 result : {@{Computers=System.Object[]; queue_name=Intensive (All Teams); number=170209-07819; private=....} installed/loaded}, @{Computers=System.Object[]; queue_name=Intensive (All Teams); number=1702....} queue_name=Contract Processing; number=170213-02900; private=1; assignee_name=; queue_id=13;....} number=170213-02903; private=0; assignee_name=Cameron McAnsh; queue_id=684; submitter=System....} offset : 100 class : Ticket.Ticket QueryTime : 0.4495868 .PARAMETER QueryLimit The number of results to return before breaking up the query into smaller chunks Useful for avoiding timeouts Defaults to 100, max 249 Higher number likely to increase chance of timeout .EXAMPLE Sets the ticket difficult by number Set-CoreTicket -Tickets "170726-03173" -SetDifficulty 1 .EXAMPLE Sets the ticket difficult by number Set-CoreTicket -Tickets "170726-03173" -SetQueueName "INTL Windows" .EXAMPLE Sets the ticket assign by sso, queue by queuename and difficult by number Set-CoreTicket -Tickets "170726-03173" -SetQueueName "INTL Windows" -SetAsigneeSso "mark4808" -SetDifficulty 1 .EXAMPLE Sets the ticket assign by id, queue by queueid and difficult by number Set-CoreTicket -Tickets "170726-03173" -SetQueueID 683 -SetAsigneeSsoId 802884 -SetDifficulty 1 Example returns queue difficulty id assignee ----- ---------- -- -------- success success 170726-03173 success .OUTPUTS The result object from Core .NOTES Written by Martin Howlett / Mark Wichall #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidatePattern("^\d{6}-\d{5}$")] [ValidateCount(1,249)] [String[]]$Tickets, [Parameter()] [ValidatePattern("^[!\#\$%&'-\.0-9@A-Z\^_``a-z\{\}~\u00C0-\u024F]{1,64}$")] #https://stackoverflow.com/questions/20690499/concrete-javascript-regex-for-accented-characters-diacritics [String]$SetAsigneeSso, [Parameter()] [ValidatePattern("^\d{4,10}$")] [Int]$SetAsigneeSsoId, [Parameter()] [ValidateNotNullOrEmpty()] [Int]$SetQueueID, [Parameter()] [ValidateNotNullOrEmpty()] [String]$SetQueueName, [Parameter()] [ValidateNotNullOrEmpty()] [Int]$SetDifficulty, [Parameter()] [ValidateNotNullOrEmpty()] [Int]$QueryLimit = 100, [Parameter()] [ValidateNotNullOrEmpty()] [Switch]$FullObject #> ) Begin { Write-Verbose "Start of $($MyInvocation.MyCommand)" } Process { #Create the dictionary that will hold our setattribute attributes [Hashtable]$SetTerms = @{} Write-Verbose $SetTerms.GetType() #Search Terms for tickets e.g. where statment for ticket based on ticket number [Hashtable]$SearchTerms = @{"Tickets" = [Array]("number", "in", [Array]($Tickets))} if ($SetAsigneeSsoId -and $SetAsigneeSso) { Write-Warning "You can only specify either SetAsigneeSsoId or SetAsigneeSso not both. Defaulting to use SetAsigneeSsoId" } #Add assign user either by ID or SSO if($SetAsigneeSsoId) { #Jsons for searching contacts by ID and seting the sub attributes for assignig a contact $SetTerms.Add("assignee",[Hashtable]@{ "class"="Contact.Contact"; "load_arg"=[Hashtable]@{ "class"="Contact.ContactWhere"; "values"= [Array]@("id", "=", "$SetAsigneeSsoId") } } ) Write-Verbose "Added AssigneeID: $SetAsigneeSsoId" } elseif($SetAsigneeSso) { #Jsons for searching contacts by SSO and seting the sub attributes for assignig a contact $SetTerms.Add("assignee",[Hashtable]@{ "class"="Contact.Contact"; "load_arg"=[Hashtable]@{ "class"="Contact.ContactWhere"; "values"= [Array]@("employee_userid", "=", "$SetAsigneeSso") } } ) Write-Verbose "Added AssigneeSsos: $SetAsigneeSsos" } #Add queue either by ID or by Name if($SetQueueID) { #Jsons for searching contacts by ID and seting the sub attributes for assignig a contact $SetTerms.Add("queue",[Hashtable]@{ "class"="Ticket.Queue"; "load_arg"=[Hashtable]@{ "class"="Ticket.QueueWhere"; "values"= [Array]@("id", "=", "$SetQueueID") } } ) Write-Verbose "Added QueueID: $SetQueueID" } #Add account numbers elseif($SetQueueName) { #Jsons for searching contacts by ID and seting the sub attributes for assignig a contact $SetTerms.Add("queue",[Hashtable]@{ "class"="Ticket.Queue"; "load_arg"=[Hashtable]@{ "class"="Ticket.QueueWhere"; "values"= [Array]@("name", "=", "$SetQueueName") } } ) Write-Verbose "Added QueueID: $SetQueueName" } #Add ticket difficulty if($SetDifficulty) { #Add to our hashtable $SetTerms.Add("difficulty",[Int]($SetDifficulty)) Write-Verbose "Added Difficulty: $SetDifficulty" } #if we dont have any Set terms, throw an error if($SetTerms.keys.count -eq 0) { Throw "No Set attribute params specified i.e. -SetQueue" } #Build our variables for the Core where statement handler [Hashtable]$WrapperParams = @{ SearchTerms = $SearchTerms SetAttribute = $SetTerms Class = "Ticket" QueryLimit = $QueryLimit } $result = Invoke-CoreWhereWrapper @WrapperParams #returns the .result value as default, full object returned if $FullObject switch used. If (-not $FullObject) { $result = $result.result } return $result } End { #Log to appstats Set-LogtoAppstats -FunctionID "Set-CoreTicket" -AppID "PoshCore" Write-Verbose "End of $($MyInvocation.MyCommand)" } } #endregion ##################### Ticket Update Functions - END #####################