diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bea433 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +.DS_Store diff --git a/PwshZendesk.psd1 b/PwshZendesk.psd1 new file mode 100644 index 0000000..8530ece --- /dev/null +++ b/PwshZendesk.psd1 @@ -0,0 +1,193 @@ +# +# Module manifest for module 'PwshZendesk' +# +# Generated by: Robert McLeod +# +# Generated on: 15/9/19 +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = '.\PwshZendesk.psm1' + + # Version number of this module. + ModuleVersion = '999.999' + + # Supported PSEditions + CompatiblePSEditions = @( + 'Core' + 'Desktop' + ) + + # ID used to uniquely identify this module + GUID = 'fd14fe05-21fb-4dfb-969b-7012632b461e' + + # Author of this module + Author = 'Telstra Purple DevSecOps' + + # Company or vendor of this module + CompanyName = 'Telstra Purple Pty Ltd' + + # Copyright statement for this module + Copyright = '(c) 2019 Telstra Purple Pty Ltd. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'Wrapper for the Zendesk Rest API' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.0' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # CLRVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( + 'Add-Tag', 'Add-ZendeskTag' + 'Connect-', 'Connect-Zendesk' + 'Export-Organization', 'Export-ZendeskOrganization' + 'Export-Sample', 'Export-ZendeskSample' + 'Export-Ticket', 'Export-ZendeskTicket' + 'Export-TicketEvent', 'Export-ZendeskTicketEvent' + 'Export-User', 'Export-ZendeskUser' + 'Get-Attachment', 'Get-ZendeskAttachment' + 'Get-AuthenticatedUser', 'Get-ZendeskAuthenticatedUser' + 'Get-Comment', 'Get-ZendeskComment' + 'Get-Connection', 'Get-ZendeskConnection' + 'Get-DeletedTicket', 'Get-ZendeskDeletedTicket' + 'Get-DeletedUser', 'Get-ZendeskDeletedUser' + 'Get-Group', 'Get-ZendeskGroup' + 'Get-GroupMembership', 'Get-ZendeskGroupMembership' + 'Get-Incident', 'Get-ZendeskIncident' + 'Get-JobStatus', 'Get-ZendeskJobStatus' + 'Get-OrganizationMembership', 'Get-ZendeskOrganizationMembership' + 'Get-Problem', 'Get-ZendeskProblem' + 'Get-SearchCount', 'Get-ZendeskSearchCount' + 'Get-SuspendedTicket', 'Get-ZendeskSuspendedTicket' + 'Get-Tag', 'Get-ZendeskTag' + 'Get-Ticket', 'Get-ZendeskTicket' + 'Get-TicketCollaborator', 'Get-ZendeskTicketCollaborator' + 'Get-TicketEmailCC', 'Get-ZendeskTicketEmailCC' + 'Get-TicketFollower', 'Get-ZendeskTicketFollower' + 'Get-TicketRelated', 'Get-ZendeskTicketRelated' + 'Get-User', 'Get-ZendeskUser' + 'Get-UserRelated', 'Get-ZendeskUserRelated' + 'Hide-Comment', 'Hide-ZendeskComment' + 'Import-Ticket', 'Import-ZendeskTicket' + 'Invoke-Method', 'Invoke-ZendeskMethod' + 'Merge-Ticket', 'Merge-ZendeskTicket' + 'Merge-User', 'Merge-ZendeskUser' + 'New-Attachment', 'New-ZendeskAttachment' + 'New-Group', 'New-ZendeskGroup' + 'New-GroupMembership', 'New-ZendeskGroupMembership' + 'New-OrganizationMembership', 'New-ZendeskOrganizationMembership' + 'New-Ticket', 'New-ZendeskTicket' + 'Remove-Attachment', 'Remove-ZendeskAttachment' + 'Remove-Group', 'Remove-ZendeskGroup' + 'Remove-GroupMembership', 'Remove-ZendeskGroupMembership' + 'Remove-OrganizationMembership', 'Remove-ZendeskOrganizationMembership' + 'Remove-SuspendedTicket', 'Remove-ZendeskSuspendedTicket' + 'Remove-Tag', 'Remove-ZendeskTag' + 'Remove-Ticket', 'Remove-ZendeskTicket' + 'Remove-User', 'Remove-ZendeskUser' + 'Restore-DeletedTicket', 'Restore-ZendeskDeletedTicket' + 'Restore-SuspendedTicket', 'Restore-ZendeskSuspendedTicket' + 'Search-', 'Search-Zendesk' + 'Set-GroupMembershipAsDefault', 'Set-ZendeskGroupMembershipAsDefault' + 'Set-OrganizationMembershipAsDefault', 'Set-ZendeskOrganizationMembershipAsDefault' + 'Set-Tag', 'Set-ZendeskTag' + 'Set-User', 'Set-ZendeskUser' + 'Test-Connection', 'Test-ZendeskConnection' + 'Update-Group', 'Update-ZendeskGroup' + 'Update-Ticket', 'Update-ZendeskTicket' + ) + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() + + # Variables to export from this module + # VariablesToExport = @() + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/Readify/PwshZendesk' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + DefaultCommandPrefix = 'Zendesk' + +} + diff --git a/PwshZendesk.psm1 b/PwshZendesk.psm1 new file mode 100644 index 0000000..2165532 --- /dev/null +++ b/PwshZendesk.psm1 @@ -0,0 +1,10 @@ + +Set-StrictMode -Version Latest + +Get-ChildItem -Path "$PSScriptRoot\functions" -Filter '*.ps1' -Recurse | ForEach-Object { + . $_.FullName + Export-ModuleMember -Function $_.BaseName +} + +$Script:NotConnectedMessage = 'No connection supplied or stored. Please either call `Connect-Zendesk` or call `Get-ZendeskConnection` and pass the result to all additional calls.' +$Script:InvalidConnection = 'Provided connection is invalid.' diff --git a/functions/Add-Tag.ps1 b/functions/Add-Tag.ps1 new file mode 100644 index 0000000..3711fdb --- /dev/null +++ b/functions/Add-Tag.ps1 @@ -0,0 +1,61 @@ + +function Add-Tag { + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + + # Unique Id of ticket to add tags to + [Parameter(Mandatory = $true, + ParameterSetName = 'Ticket')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Unique Id of organization to add tags to + [Parameter(Mandatory = $true, + ParameterSetName = 'Org')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Unique Id of user to add tags to + [Parameter(Mandatory = $true, + ParameterSetName = 'User')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Tags to add to entity + [Parameter(Mandatory = $true)] + [String[]] + $Tag, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + switch ($PSCMDlet.ParameterSetName) { + 'Ticket' { + $path = "/api/v2/tickets/$TicketId/tags.json" + } + + 'Org' { + $path = "/api/v2/organizations/$OrganizationId/tags.json" + } + + 'User' { + $path = "/api/v2/users/$UserId/tags.json" + } + } + + $body = { + tags = $Tag + } + + $result = Invoke-Method -Context $Context -Method 'Put' -Path $path -Body $body -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Connect-.ps1 b/functions/Connect-.ps1 new file mode 100644 index 0000000..b2549b2 --- /dev/null +++ b/functions/Connect-.ps1 @@ -0,0 +1,36 @@ + +function Connect- { + [OutputType([String])] + [CmdletBinding()] + Param ( + + # Zendesk subdomain + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Organization, + + # Email address of user to log in + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Username, + + # Zendesk API key retrieved from https://.zendesk.com/agent/admin/api/settings + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [SecureString] + $ApiKey + ) + + $context = Get-Connection @PSBoundParameters + + if (Test-Connection -context $context) { + $Script:Context = $context + } else { + throw $Script:InvalidConnection + } + + $true + +} diff --git a/functions/Export-Organization.ps1 b/functions/Export-Organization.ps1 new file mode 100644 index 0000000..cefc620 --- /dev/null +++ b/functions/Export-Organization.ps1 @@ -0,0 +1,25 @@ + +function Export-Organization { + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Timestamp returned by the last Export or `0` for a new incremental export + [Parameter(Mandatory = $true)] + [ValidateRange(0, [Int64]::MaxValue)] + [Int64] + $Timestamp, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/incremental/organizations.json?start_time=$Timestamp" + + $result = Invoke-Method -Context $Context -Path $path -Pagination $false -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Export-Sample.ps1 b/functions/Export-Sample.ps1 new file mode 100644 index 0000000..97a04df --- /dev/null +++ b/functions/Export-Sample.ps1 @@ -0,0 +1,31 @@ + +function Export-Sample { + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Name of the entity to get a sample export for + [Parameter(Mandatory = $true)] + [ValidateSet('tickets', 'users', 'organizations')] + [String] + $EntityName, + + # Timestamp returned by the last Export or `0` for a new incremental export + [Parameter(Mandatory = $true)] + [ValidateRange(0, [Int64]::MaxValue)] + [Int64] + $Timestamp, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/incremental/$EntityName/sample.json?start_time=$Timestamp" + + $result = Invoke-Method -Context $Context -Path $path -Pagination $false -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Export-Ticket.ps1 b/functions/Export-Ticket.ps1 new file mode 100644 index 0000000..ed78e54 --- /dev/null +++ b/functions/Export-Ticket.ps1 @@ -0,0 +1,26 @@ + +function Export-Ticket { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Timestamp returned by the last Export or `0` for a new incremental export + [Parameter(Mandatory = $true)] + [ValidateRange(0, [Int64]::MaxValue)] + [Int64] + $Timestamp, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/incremental/tickets.json?start_time=$Timestamp" + + $result = Invoke-Method -Context $Context -Path $path -Pagination $false -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Export-TicketEvent.ps1 b/functions/Export-TicketEvent.ps1 new file mode 100644 index 0000000..b22a589 --- /dev/null +++ b/functions/Export-TicketEvent.ps1 @@ -0,0 +1,41 @@ + +function Export-TicketEvent { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Timestamp returned by the last Export or `0` for a new incremental export + [Parameter(Mandatory = $true)] + [ValidateRange(0, [Int64]::MaxValue)] + [Int64] + $Timestamp, + + # Entities to sideload in the request + [Parameter(Mandatory = $false)] + [ValidateSet('comment_events')] + [String[]] + $SideLoad, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $params = @{ + Context = $Context + Path = "/api/v2/incremental/ticket_events.json?start_time=$Timestamp" + Pagination = $false + Verbose = $VerbosePreference + } + + if ($PSBoundParameters.ContainsKey('SideLoad')) { + $params.SideLoad = $SideLoad + } + + $result = Invoke-Method @params + $result + +} diff --git a/functions/Export-User.ps1 b/functions/Export-User.ps1 new file mode 100644 index 0000000..d166949 --- /dev/null +++ b/functions/Export-User.ps1 @@ -0,0 +1,26 @@ + +function Export-User { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Timestamp returned by the last Export or `0` for a new incremental export + [Parameter(Mandatory = $true)] + [ValidateRange(0, [Int64]::MaxValue)] + [Int64] + $Timestamp, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/incremental/users.json?start_time=$Timestamp" + + $result = Invoke-Method -Context $Context -Path $path -Pagination $false -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Get-Attachment.ps1 b/functions/Get-Attachment.ps1 new file mode 100644 index 0000000..1f406f4 --- /dev/null +++ b/functions/Get-Attachment.ps1 @@ -0,0 +1,26 @@ + +function Get-Attachment { + + [OutputType([PSCustomObject])] + [CmdletBinding()] + Param ( + + # Unique Id of the attachment to retrieve + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/attachments/$Id.json" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Get-AuthenticatedUser.ps1 b/functions/Get-AuthenticatedUser.ps1 new file mode 100644 index 0000000..a4203ff --- /dev/null +++ b/functions/Get-AuthenticatedUser.ps1 @@ -0,0 +1,18 @@ + +function Get-AuthenticatedUser { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $result = Invoke-Method -Context $Context -Path "/api/v2/users/me.json" -Verbose:$VerbosePreference + $result | Select-Object -Expand 'user' + +} diff --git a/functions/Get-Comment.ps1 b/functions/Get-Comment.ps1 new file mode 100644 index 0000000..853c32b --- /dev/null +++ b/functions/Get-Comment.ps1 @@ -0,0 +1,25 @@ + +function Get-Comment { + + [OutputType([String])] + [CmdletBinding()] + Param ( + + # Unique Id of the ticket to get comments for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/comments.json" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand 'comments' +} diff --git a/functions/Get-Connection.ps1 b/functions/Get-Connection.ps1 new file mode 100644 index 0000000..e3cf2a0 --- /dev/null +++ b/functions/Get-Connection.ps1 @@ -0,0 +1,35 @@ + +function Get-Connection { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + # Zendesk subdomain + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Organization, + + # Email address of user to log in + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Username, + + # Zendesk API key retrieved from https://.zendesk.com/agent/admin/api/settings + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [SecureString] + $ApiKey + ) + + $context = [PSCustomObject]@{ + Organization = $Organization + BaseUrl = "https://$Organization.zendesk.com" + Credential = [System.Management.Automation.PSCredential]::New("$Username/token", $ApiKey) + } + + $context | Add-Member -TypeName 'ZendeskContext' + $context + +} diff --git a/functions/Get-DeletedTicket.ps1 b/functions/Get-DeletedTicket.ps1 new file mode 100644 index 0000000..d37783e --- /dev/null +++ b/functions/Get-DeletedTicket.ps1 @@ -0,0 +1,19 @@ + +function Get-DeletedTicket { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = '/api/v2/deleted_tickets.json' + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand 'deleted_tickets' + +} diff --git a/functions/Get-DeletedUser.ps1 b/functions/Get-DeletedUser.ps1 new file mode 100644 index 0000000..1249e54 --- /dev/null +++ b/functions/Get-DeletedUser.ps1 @@ -0,0 +1,32 @@ + +function Get-DeletedUser { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the User to retrieve + [Parameter(Mandatory = $false)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($PSBoundParameters.ContainsKey('UserId')) { + $path = "/api/v2/deleted_users/$UserId.json" + $key = 'deleted_user' + } else { + $path = '/api/v2/deleted_users.json' + $key = 'deleted_users' + } + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand $key + +} diff --git a/functions/Get-Group.ps1 b/functions/Get-Group.ps1 new file mode 100644 index 0000000..1bbf50a --- /dev/null +++ b/functions/Get-Group.ps1 @@ -0,0 +1,78 @@ + +function Get-Group { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + + # Unique Id of the group to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'Id')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Unique Id of the User whose groups to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'UserId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Only retrieve groups that can be assigned + [Parameter(Mandatory = $true, + ParameterSetName = 'Assignable')] + [Switch] + $Assignable, + + # + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet( + 'users' + )] + [String[]] + $SideLoad, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $key = 'groups' + + switch ($PSCMDlet.ParameterSetName) { + 'Id' { + $path = "/api/v2/groups/$Id.json" + $key = 'group' + } + + 'UserId' { + $path = "/api/v2/users/$UserId/groups.json" + } + + 'Assignable' { + $path = '/api/v2/groups/assignable.json' + } + + default { + $path = '/api/v2/groups.json' + } + } + + $params = @{ + Context = $Context + Path = $path + Verbose = $VerbosePreference + } + + if ($PSBoundParameters.ContainsKey('SideLoad')) { + $params.SideLoad = $SideLoad + } + + $result = Invoke-Method @params + $result | Select-Object -Expand $key + +} diff --git a/functions/Get-GroupMembership.ps1 b/functions/Get-GroupMembership.ps1 new file mode 100644 index 0000000..84f4660 --- /dev/null +++ b/functions/Get-GroupMembership.ps1 @@ -0,0 +1,96 @@ + +function Get-GroupMembership { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + + # Unique Id of the group membership to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'Id')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Unique Id of the user to get group memberships for + [Parameter(Mandatory = $true, + ParameterSetName = 'UserId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Unique Id of the group to get membersips for + [Parameter(Mandatory = $true, + ParameterSetName = 'GroupId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $GroupId, + + # Only retrieve assignable memberships + [Parameter(Mandatory = $false, + ParameterSetName = 'GroupId')] + [Parameter(Mandatory = $false, + ParameterSetName = 'Default')] + [Switch] + $Assignable, + + # Entities to sideload in the request + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet( + 'groups', + 'users' + )] + [String[]] + $SideLoad, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $key = 'group_memberships' + + switch ($PSCMDlet.ParameterSetName) { + 'Id' { + $path = "/api/v2/group_memberships/$Id.json" + $key = 'group_membership' + } + + 'UserId' { + $path = "/api/v2/users/$UserId/group_memberships.json" + } + + 'GroupId' { + if ($Assignable) { + $path = "/api/v2/groups/$GroupId/memberships/assignable.json" + } else { + $path = "/api/v2/groups/$GroupId/memberships.json" + } + } + + default { + if ($Assignable) { + $path = '/api/v2/group_memberships/assignable.json' + } else { + $path = '/api/v2/group_memberships.json' + } + } + } + + $params = @{ + Context = $Context + Path = $path + Verbose = $VerbosePreference + } + + if ($PSBoundParameters.ContainsKey('SideLoad')) { + $params.SideLoad = $SideLoad + } + + $result = Invoke-Method @params + $result | Select-Object -Expand $key + +} diff --git a/functions/Get-Incident.ps1 b/functions/Get-Incident.ps1 new file mode 100644 index 0000000..fd22fd3 --- /dev/null +++ b/functions/Get-Incident.ps1 @@ -0,0 +1,26 @@ + +function Get-Incident { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the problem ticket to get linked incidents for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/incidents.json" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand 'tickets' + +} diff --git a/functions/Get-JobStatus.ps1 b/functions/Get-JobStatus.ps1 new file mode 100644 index 0000000..4f0c630 --- /dev/null +++ b/functions/Get-JobStatus.ps1 @@ -0,0 +1,40 @@ + +function Get-JobStatus { + + [OutputType([PSCustomObject])] + [CmdletBinding()] + Param ( + + # Unique Id of the background job whose status to retrieve + [Parameter(Mandatory = $false)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64[]] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $key = 'job_statuses' + + if ($PSBoundParameters.ContainsKey('Id')) { + + if ($Id.count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/job_statuses/show_many.json?ids=$ids" + } else { + $path = "/api/v2/job_statuses/$Id.json" + $key = 'job_status' + } + + } else { + $path = '/api/v2/job_statuses.json' + } + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand $key + +} diff --git a/functions/Get-OrganizationMembership.ps1 b/functions/Get-OrganizationMembership.ps1 new file mode 100644 index 0000000..624b3d1 --- /dev/null +++ b/functions/Get-OrganizationMembership.ps1 @@ -0,0 +1,61 @@ + +function Get-OrganizationMembership { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + + # Unique Id of the organization membership to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'Id')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Unique Id of the user to get organization memberships for + [Parameter(Mandatory = $true, + ParameterSetName = 'UserId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Unique Id of the organization to get memberships for + [Parameter(Mandatory = $true, + ParameterSetName = 'OrganizationId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $key = 'organization_memberships' + + switch ($PSCMDlet.ParameterSetName) { + 'Id' { + $path = "/api/v2/organization_memberships/$Id.json" + $key = 'organization_membership' + } + + 'UserId' { + $path = "api/v2/users/$UserId/organization_memberships.json" + } + + 'OrganizationId' { + $path = "/api/v2/organizations/$OrganizationId/organization_memberships.json" + } + + default { + $path = '/api/v2/organization_memberships.json' + } + } + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand $key + +} + diff --git a/functions/Get-Problem.ps1 b/functions/Get-Problem.ps1 new file mode 100644 index 0000000..25c6b77 --- /dev/null +++ b/functions/Get-Problem.ps1 @@ -0,0 +1,20 @@ + +function Get-Problem { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = '/api/v2/problems.json' + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand 'tickets' + +} diff --git a/functions/Get-SearchCount.ps1 b/functions/Get-SearchCount.ps1 new file mode 100644 index 0000000..7015e99 --- /dev/null +++ b/functions/Get-SearchCount.ps1 @@ -0,0 +1,27 @@ + +function Get-SearchCount { + + [OutputType([String])] + [CmdletBinding()] + Param ( + # Zendesk Query + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Query, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + Write-Debug -Message "Query: $Query" + $Query = [uri]::EscapeDataString($Query) + Write-Debug -Message "Escaped Query: $Query" + + $results = Invoke-Method -Context $Context -Path "/api/v2/search/count.json?query=$Query" -Pagination $false + $results | Select-Object -Expand 'count' + +} diff --git a/functions/Get-SuspendedTicket.ps1 b/functions/Get-SuspendedTicket.ps1 new file mode 100644 index 0000000..3c246b9 --- /dev/null +++ b/functions/Get-SuspendedTicket.ps1 @@ -0,0 +1,32 @@ + +function Get-SuspendedTicket { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the suspended ticket to retrieve + [Parameter(Mandatory = $false)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($PSBoundParameter.containsKey('UserId')) { + $path = "/api/v2/suspended_tickets/{id}.json" + $key = 'ticket' + } else { + $path = '/api/v2/suspended_tickets.json' + $key = 'tickets' + } + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand $key + +} diff --git a/functions/Get-Tag.ps1 b/functions/Get-Tag.ps1 new file mode 100644 index 0000000..b208b9b --- /dev/null +++ b/functions/Get-Tag.ps1 @@ -0,0 +1,57 @@ + +function Get-Tag { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + + # Unique Id of the ticket to get tags for + [Parameter(Mandatory = $true, + ParameterSetName = 'Ticket')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Unique Id of the organization to get tags for + [Parameter(Mandatory = $true, + ParameterSetName = 'Org')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Unique Id of the user to get tags for + [Parameter(Mandatory = $true, + ParameterSetName = 'User')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + switch ($PSCMDlet.ParameterSetName) { + 'Ticket' { + $path = "/api/v2/tickets/$TicketId/tags.json" + } + + 'Org' { + $path = "/api/v2/organizations/$OrganizationId/tags.json" + } + + 'User' { + $path = "/api/v2/users/$UserId/tags.json" + } + + default { + $path = '/api/v2/tags.json' + } + } + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result | Select-Object -Expand 'tags' + +} diff --git a/functions/Get-Ticket.ps1 b/functions/Get-Ticket.ps1 new file mode 100644 index 0000000..3e1cefb --- /dev/null +++ b/functions/Get-Ticket.ps1 @@ -0,0 +1,147 @@ + +function Get-Ticket { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + # Unique Id of the ticket to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'Id')] + [ValidateNotNullOrEmpty()] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64[]] + $Id, + + # External Id of the ticket to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'ExternalId')] + [ValidateNotNullOrEmpty()] + [String] + $ExternalId, + + # Unique Id of the user associated with this ticket. + [Parameter(Mandatory = $true, + ParameterSetName = 'Requested')] + [Parameter(Mandatory = $true, + ParameterSetName = 'Assigned')] + [Parameter(Mandatory = $true, + ParameterSetName = 'CCd')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Retrieve only tickets requested by user specified by `-UserId` + [Parameter(Mandatory = $true, + ParameterSetName = 'Requested')] + [Switch] + $Requested, + + # Retrieve only tickets assigned to user specified by `-UserId` + [Parameter(Mandatory = $true, + ParameterSetName = 'Assigned')] + [Switch] + $Assigned, + + # Retrieve only tickets with the user specified by `-UserId` CC'd + [Parameter(Mandatory = $true, + ParameterSetName = 'CCd')] + [Switch] + $CCd, + + # UniqueId of the Organization to retrieve tickets for + [Parameter(Mandatory = $true, + ParameterSetName = 'OrgId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Only retrieve recently viewed by the logged in user + [Parameter(Mandatory = $true, + ParameterSetName = 'Recent')] + [Switch] + $Recent, + + # Entities to sideload in the request + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet( + 'comment_count', + 'dates', + 'groups', + 'incident_counts', + 'last_audits', + 'metric_events', + 'metric_sets', + 'organizations', + 'sharing_agreements', + 'slas', + 'ticket_forms', + 'users' + )] + [String[]] + $SideLoad, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $key = 'tickets' + + switch ($PSCmdlet.ParameterSetName) { + 'Id' { + if ($Id.count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/tickets/show_many.json?ids=$ids" + } else { + $path = "/api/v2/tickets/$Id.json" + $key = 'ticket' + } + } + + 'ExternalId' { + $path = "/api/v2/tickets.json?external_id=$ExternalId" + } + + 'Requested' { + $path = "/api/v2/users/$UserId/tickets/requested.json" + } + + 'Assigned' { + $path = "/api/v2/users/$UserId/tickets/assigned.json" + } + + 'CCd' { + $path = "/api/v2/users/$UserId/tickets/ccd.json" + } + + 'OrgId' { + $path = "/api/v2/organizations/$OrganizationId/tickets.json" + } + + 'Recent' { + $path = '/api/v2/tickets/recent.json' + } + + default { + $path = '/api/v2/tickets.json' + } + } + + $params = @{ + Context = $Context + Path = $path + Verbose = $VerbosePreference + } + + if ($PSBoundParameters.ContainsKey('SideLoad')) { + $params.SideLoad = $SideLoad + } + + $result = Invoke-Method @params + $result | Select-Object -Expand $key + +} + diff --git a/functions/Get-TicketCollaborator.ps1 b/functions/Get-TicketCollaborator.ps1 new file mode 100644 index 0000000..356ce60 --- /dev/null +++ b/functions/Get-TicketCollaborator.ps1 @@ -0,0 +1,26 @@ + +function Get-TicketCollaborator { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the ticket to get Collaborators for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/collaborators.json" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Get-TicketEmailCC.ps1 b/functions/Get-TicketEmailCC.ps1 new file mode 100644 index 0000000..96d5ddb --- /dev/null +++ b/functions/Get-TicketEmailCC.ps1 @@ -0,0 +1,26 @@ + +function Get-TicketEmailCC { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the ticket to get email ccs for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/email_ccs" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Get-TicketFollower.ps1 b/functions/Get-TicketFollower.ps1 new file mode 100644 index 0000000..284e8b5 --- /dev/null +++ b/functions/Get-TicketFollower.ps1 @@ -0,0 +1,26 @@ + +function Get-TicketFollower { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the ticket to get followers for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/followers" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Get-TicketRelated.ps1 b/functions/Get-TicketRelated.ps1 new file mode 100644 index 0000000..894a2f9 --- /dev/null +++ b/functions/Get-TicketRelated.ps1 @@ -0,0 +1,26 @@ + +function Get-TicketRelated { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the ticket to get related information for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/related.json" + + $result = Invoke-Method -Context $Context -Path $path -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Get-User.ps1 b/functions/Get-User.ps1 new file mode 100644 index 0000000..2d5f9a6 --- /dev/null +++ b/functions/Get-User.ps1 @@ -0,0 +1,142 @@ + +function Get-User { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param ( + + # Unique Id of the user to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'Id')] + [ValidateNotNullOrEmpty()] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64[]] + $Id, + + # External Id of the user to retrieve + [Parameter(Mandatory = $true, + ParameterSetName = 'ExternalId')] + [ValidateNotNullOrEmpty()] + [String[]] + $ExternalId, + + # Unique Id of the group to retrieve users for + [Parameter(Mandatory = $true, + ParameterSetName = 'GroupId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $GroupId, + + # Unique Id of the organization to retrieve users for + [Parameter(Mandatory = $true, + ParameterSetName = 'OrgId')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Role to retrieve users for + [Parameter(Mandatory = $false, + ParameterSetName = 'GroupId')] + [Parameter(Mandatory = $false, + ParameterSetName = 'OrgId')] + [Parameter(Mandatory = $false, + ParameterSetName = 'Default')] + [ValidateSet('end-user', 'agent', 'admin')] + [String[]] + $Role, + + # Unique Id of the custom role to retrieve users for + [Parameter(Mandatory = $false, + ParameterSetName = 'GroupId')] + [Parameter(Mandatory = $false, + ParameterSetName = 'OrgId')] + [Parameter(Mandatory = $false, + ParameterSetName = 'Default')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $RoleId, + + # Entities to sideload in the request + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet( + 'abilities', + 'groups', + 'identities', + 'open_ticket_count', + 'organizations', + 'roles' + )] + [String[]] + $SideLoad, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $key = 'users' + + switch ($PSCmdlet.ParameterSetName) { + 'Id' { + if ($Id.count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/users/show_many.json?ids=$ids" + } else { + $path = "/api/v2/users/$Id.json" + $key = 'user' + } + } + + 'ExternalId' { + $ids = $ExternalId -join ',' + $path = "/api/v2/users/show_many.json?external_ids=$ids" + } + + 'GroupId' { + $path = "/api/v2/groups/$GroupId/users.json" + } + + 'OrgId' { + $path = "/api/v2/organizations/$OrganizationId/users.json" + } + + default { + $path = '/api/v2/users.json' + } + } + + Write-Debug -Message "Path: $path" + + if ($PSBoundParameters.ContainsKey('Role')) { + if ($Role.count -gt 1) { + $roles = $Role -join '&role[]=' + $path += "?role[]=$roles" + } else { + $path += "?role=$Role" + } + + Write-Debug -Message "Path: $path" + } + + if ($PSBoundParameters.ContainsKey('RoleId')) { + $path += "?permission_set=$RoleId" + Write-Debug -Message "Path: $path" + } + + $params = @{ + Context = $Context + Path = $path + Verbose = $VerbosePreference + } + + if ($PSBoundParameters.ContainsKey('SideLoad')) { + $params.SideLoad = $SideLoad + } + + $result = Invoke-Method @params + $result | Select-Object -Expand $key + +} diff --git a/functions/Get-UserRelated.ps1 b/functions/Get-UserRelated.ps1 new file mode 100644 index 0000000..d353ff2 --- /dev/null +++ b/functions/Get-UserRelated.ps1 @@ -0,0 +1,24 @@ + +function Get-UserRelated { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Unique Id of the user to get related information for + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $result = Invoke-Method -Path "/api/v2/users/$UserId/related.json" -Context $Context -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Hide-Comment.ps1 b/functions/Hide-Comment.ps1 new file mode 100644 index 0000000..bc9fbbb --- /dev/null +++ b/functions/Hide-Comment.ps1 @@ -0,0 +1,31 @@ + +function Hide-Comment { + + [OutputType([String])] + [CmdletBinding()] + Param ( + + # Unique Id of the comment to hide + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Unique Id of the ticket the comment resides on + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/tickets/$TicketId/comments/$Id/make_private.json" + + $result = Invoke-Method -Context $Context -Method 'Put' -Path $path -Verbose:$VerbosePreference + $result +} diff --git a/functions/Import-Ticket.ps1 b/functions/Import-Ticket.ps1 new file mode 100644 index 0000000..00ace99 --- /dev/null +++ b/functions/Import-Ticket.ps1 @@ -0,0 +1,36 @@ + +function Import-Ticket { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Full ticket object to import + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [PSCustomObject[]] + $Ticket, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Ticket.count -gt 1) { + $path = '/api/v2/imports/tickets/create_many.json' + $body = @{ + tickets = $Ticket + } + } else { + $path = '/api/v2/imports/tickets.json' + $body = @{ + ticket = $Ticket[0] + } + } + + $result = Invoke-Method -Context $Context -Method 'Post' -Path $path -Body $body -Verbose:$VerbosePreference + $result + +} diff --git a/functions/Invoke-Method.ps1 b/functions/Invoke-Method.ps1 new file mode 100644 index 0000000..5d112e2 --- /dev/null +++ b/functions/Invoke-Method.ps1 @@ -0,0 +1,195 @@ + +function Invoke-Method { + + [OutputType([PSCustomObject])] + [CMDletBinding()] + Param ( + + # Rest Method + [Parameter(Mandatory = $false)] + [ValidateSet('Delete', 'Get', 'Post', 'Put')] + [String] + $Method = 'Get', + + # Path of the rest request. eg `/Account` + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + # Body of the Rest call + [Parameter(Mandatory = $false)] + [PSCustomObject] + $Body, + + # Content-Type of the body content + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [String] + $ContentType = 'application/json', + + # Whether to perform pagination. + [Parameter(Mandatory = $false)] + [Boolean] + $Pagination = $true, + + # Property to sort by. Set to `$null` for no sorting. Defaults to `created_at` + [Parameter(Mandatory = $false)] + [AllowEmptyString()] + [String] + $SortBy = 'created_at', + + # Whether to retry when rate limited. + [Parameter(Mandatory = $false)] + [Boolean] + $Retry = $true, + + # Entities to sideload in the request + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet( + 'abilities', + 'app_installation', + 'brands', + 'categories', + 'comment_count', + 'comment_events', + 'dates', + 'groups', + 'identities', + 'incident_counts', + 'last_audits', + 'metric_events', + 'metric_sets', + 'open_ticket_count', + 'organizations', + 'permissions', + 'roles', + 'sharing_agreements', + 'slas', + 'ticket_forms', + 'tickets', + 'usage_1h', + 'usage_24h', + 'usage_30d', + 'usage_7d', + 'users' + )] + [String[]] + $SideLoad, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + + ) + + # Determine the context + if ($null -eq $Context) { + if (Test-Path Variable:\Script:Context) { + $Context = $Script:Context + } else { + throw $Script:NotConnectedMessage + } + } + + $params = @{ + Method = $Method + Headers = @{ + Accept = 'application/json' + } + } + + if ($PSVersionTable.PSEdition -eq 'Core') { + # PS Core added native Basic Auth support + $params.Credential = $Context.Credential + $params.Authentication = 'Basic' + } else { + # PS Desktop requires manual header creation. Basic auth is only supported by challenge. + $raw = '{0}/token:{1}' -f $Context.Credential.username, $Context.Credential.GetNetworkCredential().password + $bytes = [System.Text.Encoding]::Unicode.GetBytes($raw) + $encoded =[Convert]::ToBase64String($bytes) + + $params.Headers.Authentication = "basic: $encoded" + } + + if ($PSBoundParameters.ContainsKey('Body')) { + if ($ContentType -eq 'application/json') { + $params.Body = $Body | ConvertTo-Json -Compress + } else { + $params.Body = $Body + } + $params.ContentType = $ContentType + } + + $uri = $Context.BaseUrl + $Path + + while (-not [String]::IsNullOrEmpty($uri)) { + Write-Debug -Message "Uri before SortBy: $uri" + if (-not [String]::IsNullOrEmpty($SortBy) -and $uri -notmatch 'sort_by=') { + if ($uri -match '\?') { + $uri += "&sort_by=$SortBy" + } else { + $uri += "?sort_by=$SortBy" + } + } + + Write-Debug -Message "Uri before Sideload: $uri" + if (-not [String]::IsNullOrEmpty($SideLoad) -and $uri -notmatch 'include=') { + $sideloadJoined = $SideLoad -join ',' + if ($uri -match '\?') { + $uri += "&include=$sideloadJoined" + } else { + $uri += "?include=$sideloadJoined" + } + } + + try { + $result = Invoke-RestMethod @params -Uri $uri -Verbose:$VerbosePreference + $result + } catch { + $errorRecord = $_ + } + + # 429 Too Many Requests - Exponential rety with jitter. + $multiplier = 1 + while ($Retry -and (Test-Path -Path Variable:/errorRecord) -and $null -ne $errorRecord -and + $errorRecord.Exception -is [Microsoft.PowerShell.Commands.HttpResponseException] -and + $errorRecord.Exception.Response.StatusCode.value__ -eq 429) { + + $retryAfter = $errorRecord.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds + $retryAfter = 1 + + $multiplier *= 2 + $jitter = Get-Random -Minimum 0.0 -Maximum 1.0 + + $sleep = $retryAfter + $multiplier + $jitter + Write-Warning -Message "Too many requests! Retrying after $sleep seconds." + Start-Sleep -Seconds $sleep + + $errorRecord = $null + try { + $result = Invoke-RestMethod @params -Uri $uri -Verbose:$VerbosePreference + $result + } catch { + $errorRecord = $_ + } + } + + if ((Test-Path -Path Variable:\errorRecord) -and $null -ne $errorRecord) { + # Get just the message without stack trace, category info, or qualified error + $errorMessage = $errorRecord.ToString() + Get-PSCallStack | Foreach-Object { $errorMessage += "`n" + $_.Command + ': line ' + $_.ScriptLineNumber } + throw $errorMessage + } + + $uri = $null + if ($Pagination) { + if ($null -ne $result -and (Get-Member -InputObject $result -Name 'next_page' -MemberType Properties)) { + $uri = $result.next_page + } + } + } +} diff --git a/functions/Merge-User.ps1 b/functions/Merge-User.ps1 new file mode 100644 index 0000000..a7179dd --- /dev/null +++ b/functions/Merge-User.ps1 @@ -0,0 +1,39 @@ + +function Merge-User { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of the user to merge + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Unique Id of the user to merge into + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TargetUserId, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/users/$UserId/merge.json" + $body = @{ + user = @{ + id = $TargerUserId + } + } + + if ($PSCmdlet.ShouldProcess("$UserId => $TargetUserId", 'Merge user.')) { + $result = Invoke-Method -Context $Context -Method 'Put' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/New-Group.ps1 b/functions/New-Group.ps1 new file mode 100644 index 0000000..75209b3 --- /dev/null +++ b/functions/New-Group.ps1 @@ -0,0 +1,33 @@ + +function New-Group { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # The name of the group. + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = '/api/v2/groups.json' + $body = @{ + group = @{ + name = $Name + } + } + + if ($PSCmdlet.ShouldProcess($Name, 'Create Group')) { + $result = Invoke-Method -Context $Context -Method 'Post' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/New-GroupMembership.ps1 b/functions/New-GroupMembership.ps1 new file mode 100644 index 0000000..d894aac --- /dev/null +++ b/functions/New-GroupMembership.ps1 @@ -0,0 +1,46 @@ + +function New-GroupMembership { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # The id of an agent + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # The id of a group + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $GroupId, + + # If true, tickets assigned directly to the agent will assume this membership's group. + [Parameter(Mandatory = $false)] + [Switch] + $Default, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = 'api/v2/group_memberships.json' + $body = @{ + group_membership = @{ + user_id = $UserId + group_id = $GroupId + default = $Default + } + } + + if ($PSCmdlet.ShouldProcess($UserId, "Assign to Group: $GroupId")) { + $result = Invoke-Method -Context $Context -Method 'Post' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/New-OrganizationMembership.ps1 b/functions/New-OrganizationMembership.ps1 new file mode 100644 index 0000000..d0cd174 --- /dev/null +++ b/functions/New-OrganizationMembership.ps1 @@ -0,0 +1,46 @@ + +function New-OrganizationMembership { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # The ID of the user for whom this memberships belongs + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # he ID of the organization associated with this user, in this membership + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Denotes whether this is the default organization membership for the user. + [Parameter(Mandatory = $false)] + [Switch] + $Default, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = 'api/v2/group_memberships.json' + $body = @{ + group_membership = @{ + user_id = $UserId + organization_id = $OrganizationId + default = $Default + } + } + + if ($PSCmdlet.ShouldProcess($UserId, "Assign to Org: $OrganizationId")) { + $result = Invoke-Method -Context $Context -Method 'Post' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-Attachment.ps1 b/functions/Remove-Attachment.ps1 new file mode 100644 index 0000000..73f314e --- /dev/null +++ b/functions/Remove-Attachment.ps1 @@ -0,0 +1,28 @@ + +function Remove-Attachment { + + [OutputType([PSCustomObject])] + [CmdletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique token of the attachment to delete + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Token, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/uploads/$Token.json" + + if ($PSCmdlet.ShouldProcess($Token, 'Delete attachment')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-Group.ps1 b/functions/Remove-Group.ps1 new file mode 100644 index 0000000..3f2db37 --- /dev/null +++ b/functions/Remove-Group.ps1 @@ -0,0 +1,28 @@ + +function Remove-Group { + + [OutputType([PSCustomObject])] + [CmdletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of group to delete + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/groups/$Id.json" + + if ($PSCmdlet.ShouldProcess($Id, "Delete Group")) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-GroupMembership.ps1 b/functions/Remove-GroupMembership.ps1 new file mode 100644 index 0000000..61178ef --- /dev/null +++ b/functions/Remove-GroupMembership.ps1 @@ -0,0 +1,34 @@ + +function Remove-GroupMembership { + + [OutputType([PSCustomObject])] + [CmdletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of group membership to remove + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [ValidateNotNullOrEmpty()] + [Int64[]] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Id.count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/group_memberships/destroy_many.json?ids=$ids" + } else { + $path = "/api/v2/group_memberships/$Id.json" + } + + if ($PSCmdlet.ShouldProcess("$Id", 'Delete Group Memberships')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-OrganizationMembership.ps1 b/functions/Remove-OrganizationMembership.ps1 new file mode 100644 index 0000000..d1259f5 --- /dev/null +++ b/functions/Remove-OrganizationMembership.ps1 @@ -0,0 +1,34 @@ + +function Remove-OrganizationMembership { + + [OutputType([PSCustomObject])] + [CmdletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of the organization membership to remove + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [ValidateNotNullOrEmpty()] + [Int64[]] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Id.count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/organization_memberships/destroy_many.json?ids=$ids" + } else { + $path = "/api/v2/organization_memberships/$Id.json" + } + + if ($PSCmdlet.ShouldProcess("$Id", 'Delete Organization Membership')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-SuspendedTicket.ps1 b/functions/Remove-SuspendedTicket.ps1 new file mode 100644 index 0000000..236290f --- /dev/null +++ b/functions/Remove-SuspendedTicket.ps1 @@ -0,0 +1,33 @@ + +function Get-SuspendedTicket { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + # Unique Id of suspended ticket to retrieve + [Parameter(Mandatory = $false)] + [ValidateRange(1, [Int64]::MaxValue)] + [ValidateNotNullOrEmpty()] + [Int64[]] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Id.Count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/suspended_tickets/destroy_many.json?ids=$ids" + } else { + $path = "/api/v2/suspended_tickets/$Id.json" + } + + if ($PSCmdlet.ShouldProcess("$Id", 'Delete Suspended Ticket')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-Tag.ps1 b/functions/Remove-Tag.ps1 new file mode 100644 index 0000000..cf31a45 --- /dev/null +++ b/functions/Remove-Tag.ps1 @@ -0,0 +1,64 @@ + +function Remove-Tag { + + [OutputType([PSCustomObject])] + [CmdletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of the ticket to remove tags from + [Parameter(Mandatory = $true, + ParameterSetName = 'Ticket')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Unique Id of the organization to remove tags from + [Parameter(Mandatory = $true, + ParameterSetName = 'Org')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Unique Id of the user to remove tags from + [Parameter(Mandatory = $true, + ParameterSetName = 'User')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Tags to remove from the entity + [Parameter(Mandatory = $true)] + [String[]] + $Tag, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + switch ($PSCMDlet.ParameterSetName) { + 'Ticket' { + $path = "/api/v2/tickets/$TicketId/tags.json" + } + + 'Org' { + $path = "/api/v2/organizations/$OrganizationId/tags.json" + } + + 'User' { + $path = "/api/v2/users/$UserId/tags.json" + } + } + + $body = { + tags = $Tag + } + + if ($PSCmdlet.ShouldProcess("$Tag", 'Remove Tag')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-Ticket.ps1 b/functions/Remove-Ticket.ps1 new file mode 100644 index 0000000..d73c48d --- /dev/null +++ b/functions/Remove-Ticket.ps1 @@ -0,0 +1,47 @@ + +function Remove-Ticket { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of the ticket to delete + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64[]] + $Id, + + # Permanently deleted a deleted ticket + [Parameter(Mandatory = $false)] + [Switch] + $Permanent, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Id.Count -gt 1) { + $ids = $Id -join ',' + + if ($Permanent) { + $path = "/api/v2/deleted_tickets/destroy_many?ids=$ids" + } else { + $path = "/api/v2/tickets/destroy_many.json?ids=$ids" + } + } else { + if ($Permanent) { + $path = "/api/v2/deleted_tickets/$Id.json" + } else { + $path = "/api/v2/tickets/$Id.json" + } + } + + if ($PSCmdlet.ShouldProcess("$Id", 'Delete Ticket')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Remove-User.ps1 b/functions/Remove-User.ps1 new file mode 100644 index 0000000..39bdf41 --- /dev/null +++ b/functions/Remove-User.ps1 @@ -0,0 +1,37 @@ + +function Remove-User { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of the user to delete + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Permanently delete soft deleted user + [Parameter(Mandatory = $false)] + [Switch] + $Permanent, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Permanent) { + $path = "/api/v2/deleted_users/$UserId.json" + } else { + $path = "/api/v2/users/$UserId.json" + } + + if ($PSCmdlet.ShouldProcess($UserId, 'Delete User')) { + $result = Invoke-Method -Context $Context -Method 'Delete' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Restore-DeletedTicket.ps1 b/functions/Restore-DeletedTicket.ps1 new file mode 100644 index 0000000..5ca2219 --- /dev/null +++ b/functions/Restore-DeletedTicket.ps1 @@ -0,0 +1,32 @@ + +function Restore-DeletedTicket { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of soft deleted ticket to restore + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64[]] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Id.Count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/deleted_tickets/restore_many?ids=$ids" + } else { + $path = "/api/v2/deleted_tickets/$Id/restore.json" + } + + if ($PSCmdlet.ShouldProcess("$Id", 'Restore deleted ticket.')) { + $result = Invoke-Method -Context $Context -Method 'Put' -Path $path -Verbose:$VerbosePreference + $result + } +} diff --git a/functions/Restore-SuspendedTicket.ps1 b/functions/Restore-SuspendedTicket.ps1 new file mode 100644 index 0000000..8853929 --- /dev/null +++ b/functions/Restore-SuspendedTicket.ps1 @@ -0,0 +1,33 @@ + +function Restore-SuspendedTicket { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + # Unique Id of suspended ticket to restore + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [ValidateNotNullOrEmpty()] + [Int64[]] + $Id, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($Id.Count -gt 1) { + $ids = $Id -join ',' + $path = "/api/v2/suspended_tickets/recover_many.json?ids=$ids" + } else { + $path = "/api/v2/suspended_tickets/$Id/recover.json" + } + + if ($PSCmdlet.ShouldProcess("$Id", 'Restore suspended ticket.')) { + $result = Invoke-Method -Context $Context -Method 'Put' -Path $path -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Search-.ps1 b/functions/Search-.ps1 new file mode 100644 index 0000000..1707bb7 --- /dev/null +++ b/functions/Search-.ps1 @@ -0,0 +1,34 @@ + +function Search- { + <# + .Synopsis + Searches Zendesk. + .DESCRIPTION + Searches Zendesk using the universal search api. + .EXAMPLE + Search-Zendesk -Query 'type:user user@company.com' + #> + [OutputType([String])] + [CmdletBinding()] + Param ( + # Zendesk Search Query + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Query, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + Write-Debug -Message "Query: $Query" + $Query = [uri]::EscapeDataString($Query) + Write-Debug -Message "Escaped Query: $Query" + + $results = Invoke-Method -Context $Context -Path "/api/v2/search.json?query=$Query" + $results.results + +} diff --git a/functions/Set-Tag.ps1 b/functions/Set-Tag.ps1 new file mode 100644 index 0000000..3fe8da2 --- /dev/null +++ b/functions/Set-Tag.ps1 @@ -0,0 +1,64 @@ + +function Set-Tag { + + [OutputType([PSCustomObject])] + [CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true)] + Param ( + + # Unique Id of ticket to set tags for + [Parameter(Mandatory = $true, + ParameterSetName = 'Ticket')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $TicketId, + + # Unique Id of organization to set tags for + [Parameter(Mandatory = $true, + ParameterSetName = 'Org')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # Unique Id of user to set tags for + [Parameter(Mandatory = $true, + ParameterSetName = 'User')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $UserId, + + # Tags to set for the entity. Existing tags not included here will be removed. + [Parameter(Mandatory = $true)] + [String[]] + $Tag, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + switch ($PSCMDlet.ParameterSetName) { + 'Ticket' { + $path = "/api/v2/tickets/$TicketId/tags.json" + } + + 'Org' { + $path = "/api/v2/organizations/$OrganizationId/tags.json" + } + + 'User' { + $path = "/api/v2/users/$UserId/tags.json" + } + } + + $body = { + tags = $Tag + } + + if ($PSCmdlet.ShouldProcess("$TicketId$OrganizationId$UserId", "Set Tags: $Tag")) { + $result = Invoke-Method -Context $Context -Method 'POST' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Set-User.ps1 b/functions/Set-User.ps1 new file mode 100644 index 0000000..8f50131 --- /dev/null +++ b/functions/Set-User.ps1 @@ -0,0 +1,248 @@ + +function Set-User { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # User Object to set + [Parameter(Mandatory = $true, + ParameterSetName = 'Object')] + [ValidateNotNullOrEmpty()] + [PSCustomObject[]] + $User, + + # The user's primary email address. + [Parameter(Mandatory = $true, + ParameterSetName = 'Properties')] + [ValidatePattern('@')] + [String] + $Email, + + # The user's name. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + # An alias displayed to end users. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Alias, + + # A custom role if the user is an agent on the Enterprise plan. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $CustomRoleId, + + # Any details you want to store about the user. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Details, + + # A unique identifier from another system. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $ExternalId, + + # The user's locale. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Locale, + + # The user's language identifier. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $LocaleId, + + # Designates whether the user has forum moderation capabilities. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [boolean] + $Moderator, + + # Any notes you want to store about the user. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Notes, + + # If the user can only create private comments. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [Boolean] + $OnlyPrivateComments, + + # The id of the user's organization. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $OrganizationId, + + # The id of the user's default group. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $DefaultGroupId, + + # The user's primary phone number. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Phone, + + # The user's profile picture represented as an Attachment object. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [PSCustomObject] + $Photo, + + # If the agent has any restrictions. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [Boolean] + $RestrictedAgent, + + # The user's role. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Role, + + # The user's signature. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Signature, + + # If the agent is suspended. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [Boolean] + $Suspended, + + # The user's tags. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String[]] + $Tags, + + # Specifies which tickets the user has access to. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $TicketRestriction, + + # The user's time zone. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $TimeZone, + + # Values of custom fields in the user's profile. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [HashTable] + $UserFields, + + # The user's primary identity is verified or not. + [Parameter(Mandatory = $false, + ParameterSetName = 'Properties')] + [Boolean] + $Verified, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + if ($PSCmdlet.ParameterSetName -eq 'Properties') { + + $path = '/api/v2/users/create_or_update.json' + $body = @{ + user = @{ + email = $Email + } + } + + $map = @{ + name = 'Name' + alias = 'Alias' + custom_role_id = 'CustomRoleId' + details = 'Details' + external_id = 'ExternalId' + locale = 'Locale' + locale_id = 'LocaleId' + moderator = 'Moderator' + notes = 'Notes' + only_private_comments = 'OnlyPrivateComments' + organization_id = 'OrganizationId' + default_group_id = 'DefaultGroupId' + phone = 'Phone' + photo = 'Photo' + restricted_agent = 'RestrictedAgent' + role = 'Role' + signature = 'Signature' + suspended = 'Suspended' + tags = 'Tags' + ticket_restriction = 'TicketRestriction' + time_zone = 'TimeZone' + user_fields = 'UserFields' + verified = 'Verified' + } + + foreach ($item in $map) { + $property = $item.key + $parameter = $item.value + if ($PSBoundParameters.ContainsKey($parameter)) { + $body.user[$property] = $PSBoundParameters.$parameter + } + } + + } else { + + if ($User.count -gt 1) { + $path = '/api/v2/users/create_or_update_many.json' + $body = @{ + users = $User + } + } else { + $path = '/api/v2/users/create_or_update.json' + $body = @{ + user = $User[0] + } + } + + } + + if ($PSCmdlet.ShouldProcess('Set Users')) { + $result = Invoke-Method -Context $Context -Method 'Post' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/functions/Test-Connection.ps1 b/functions/Test-Connection.ps1 new file mode 100644 index 0000000..2c12b4c --- /dev/null +++ b/functions/Test-Connection.ps1 @@ -0,0 +1,26 @@ + +function Test-Connection { + <# + .Synopsis + Checks that api credentials have been stored. + .DESCRIPTION + Checks that api credentials have been stored. + .EXAMPLE + if (Test-ZendeskConnection) { + Search-Zendesk @searchParams + } + #> + [OutputType([Bool])] + [CmdletBinding()] + Param( + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $null = Get-AuthenticatedUser -Context $Context + $true + +} diff --git a/functions/Update-Group.ps1 b/functions/Update-Group.ps1 new file mode 100644 index 0000000..5d08cf4 --- /dev/null +++ b/functions/Update-Group.ps1 @@ -0,0 +1,40 @@ + +function Update-Group { + + [OutputType([PSCustomObject])] + [CMDletBinding(SupportsShouldProcess = $true)] + Param ( + + # Unique Id of the group to update + [Parameter(Mandatory = $true)] + [ValidateRange(1, [Int64]::MaxValue)] + [Int64] + $Id, + + # The new name of the group. + [Parameter(Mandatory = $true, + ParameterSetName = 'Properties')] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + # Zendesk Connection Context from `Get-ZendeskConnection` + [Parameter(Mandatory = $false)] + [PSTypeName('ZendeskContext')] + [PSCustomObject] + $Context = $null + ) + + $path = "/api/v2/groups/$Id.json" + $body = @{ + group = @{ + name = $Name + } + } + + if ($PSCmdlet.ShouldProcess($Id, 'Update Group Name.')) { + $result = Invoke-Method -Context $Context -Method 'Put' -Path $path -Body $body -Verbose:$VerbosePreference + $result + } + +} diff --git a/tests/Invoke-Method.tests.ps1 b/tests/Invoke-Method.tests.ps1 new file mode 100644 index 0000000..59d49f0 --- /dev/null +++ b/tests/Invoke-Method.tests.ps1 @@ -0,0 +1,226 @@ + +Import-Module "$PSScriptRoot/../PwshZendesk.psm1" -Force + +Describe 'Invoke-Method' { + + InModuleScope PwshZendesk { + + $IRM = Get-Command -Name 'Invoke-RestMethod' -Module 'Microsoft.PowerShell.Utility' + + $context = @{ + Organization = 'httpbin' + BaseUrl = 'https://httpbin.org' + Credential = [System.Management.Automation.PSCredential]::New("email", ('api-key' | ConvertTo-SecureString -AsPlainText -Force)) + } + $context | Add-Member -TypeName 'ZendeskContext' + + # Needs to run before Invoke-RestMethod is mocked. + It 'Passes the creds using Basic Auth' { + { Invoke-Method -Context $context -Path '/basic-auth/email/api-key' } | Should -Not -Throw + { Invoke-Method -Context $context -Path '/basic-auth/different/creds' } | Should -Throw + } + + $context = @{ + Organization = 'company' + BaseUrl = 'https://company.testdesk.com' + Credential = [System.Management.Automation.PSCredential]::New("email", ('api-key' | ConvertTo-SecureString -AsPlainText -Force)) + } + $context | Add-Member -TypeName 'ZendeskContext' + + Mock -ModuleName PwshZendesk Invoke-RestMethod {} + + It 'Uses the baseUrl' { + Invoke-Method -Context $context -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Uri -match 'https://company.testdesk.com'} -Scope It + } + + It 'Uses the path' { + Invoke-Method -Context $context -Path '/Thing' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Uri -match '/Thing'} -Scope It + } + + It 'Accepts JSON' { + Invoke-Method -Context $context -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { + $Headers.ContainsKey('Accept') -and $Headers.Accept -match 'application\/json' + } -Scope It + } + + It 'Cascades Verbose' { + Invoke-Method -Context $context -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { -not $VerbosePreference } -Scope It + + Invoke-Method -Context $context -Path '/' -Verbose + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter { $VerbosePreference } -Scope It + + Invoke-Method -Context $context -Path '/' -Verbose:$false + Assert-MockCalled Invoke-RestMethod -Exactly 2 -ParameterFilter { -not $VerbosePreference } -Scope It + } + + It 'Default Method: Get' { + Invoke-Method -Context $context -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Method -eq 'Get'} -Scope It + } + + It 'Explicit Method: Get' { + Invoke-Method -Context $context -Method 'Get' -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Method -eq 'Get'} -Scope It + } + + It 'Explicit Method: Post' { + Invoke-Method -Context $context -Method 'Post' -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Method -eq 'Post'} -Scope It + } + + It 'Explicit Method: Put' { + Invoke-Method -Context $context -Method 'Put' -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Method -eq 'Put'} -Scope It + } + + It 'Explicit Method: Delete' { + Invoke-Method -Context $context -Method 'Delete' -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Method -eq 'Delete'} -Scope It + } + + It 'Throws with no connection' { + { Invoke-Method -Path '/' } | Should -Throw + } + + It 'Throws with no invalid connection type' { + { Invoke-Method -Path '/' -Context [PSCustomObject]@{ + Organization = 'company' + BaseUrl = 'https://company.testdesk.com' + Credential = $null + } } | Should -Throw + } + + It 'Converts Body to JSON by default' { + Invoke-Method -Context $context -Method 'Post' -Path '/' -Body ([PSCustomObject]@{A=1; B=2; C=3}) + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Body -eq '{"A":1,"B":2,"C":3}'} -Scope It + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$ContentType -eq 'application/json'} -Scope It + } + + It 'Leaves Body alone with explicit ContentType' { + Invoke-Method -Context $context -Method 'Post' -Path '/' -Body 'The Body' -ContentType 'text/plain' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Body -eq 'The Body'} -Scope It + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$ContentType -eq 'text/plain'} -Scope It + } + + It 'Sorts by created_at by default' { + Invoke-Method -Context $context -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Uri -match 'sort_by=created_at'} -Scope It + } + + It 'Sorts by any supplied property' { + Invoke-Method -Context $context -Path '/' -SortBy 'name' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Uri -match 'sort_by=name'} -Scope It + } + + It 'Doesn;t sort when SortBy is set to $null' { + Invoke-Method -Context $context -Path '/' -SortBy $null + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Uri -notmatch 'sort_by'} -Scope It + } + + It 'Can sort if path includes query variables' { + Invoke-Method -Context $context -Path '/thing?name=jim' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -ParameterFilter {$Uri -match '\?name=jim&sort_by'} -Scope It + } + + Mock -ModuleName PwshZendesk Invoke-RestMethod { & $IRM 'httpstat.us/404' } + + It 'Throws on a 404' { + { Invoke-Method -Context $context -Path '/' } | Should -Throw + } + + It 'Passes on 404 error message' { + try { Invoke-Method -Context $context -Path '/' } catch { $E = $_} + $E | Should -Match '404 \(Not Found\)' + } + + Mock -ModuleName PwshZendesk Invoke-RestMethod { & $IRM 'httpstat.us/400' } + + It 'Passes on 400 error message' { + try { Invoke-Method -Context $context -Path '/' } catch { $E = $_} + $E | Should -Match '400 \(Bad Request\)' + } + + Mock -ModuleName PwshZendesk Invoke-RestMethod { & $IRM 'httpstat.us/500' } + + It 'Passes on 500 error message' { + try { Invoke-Method -Context $context -Path '/' } catch { $E = $_} + $E | Should -Match '500 \(Internal Server Error\)' + } + + It 'Passes on Calling function' { + try { Invoke-Method -Context $context -Path '/' } catch { $E = $_} + $E | Should -Match ': line 1\d\d' + } + + Mock -ModuleName PwshZendesk Invoke-RestMethod { + if ($Uri -match 'idx=([0-9])') { + $idx = [int]$Matches[1] + if ($idx -eq 5) { + [PSCustomObject]@{ + next_page = $null + } + } else { + $idx += 1 + [PSCustomObject]@{ + next_page = $Uri -replace 'idx=[0-9]', "idx=$idx" + } + } + } else { + throw 'no idx' + } + } + + It 'Pages by default' { + Invoke-Method -Context $context -Path '/Thing?idx=1' + Assert-MockCalled Invoke-RestMethod -Exactly 5 -Scope It + } + + It 'Pages explicitly' { + Invoke-Method -Context $context -Path '/Thing?idx=1' -Pagination $true + Assert-MockCalled Invoke-RestMethod -Exactly 5 -Scope It + } + + It 'Does not page when asked not to' { + Invoke-Method -Context $context -Path '/Thing?idx=1' -Pagination $false + Assert-MockCalled Invoke-RestMethod -Exactly 1 -Scope It + } + + $Script:attempts = 0 + Mock -ModuleName PwshZendesk Invoke-RestMethod { + $Script:attempts += 1 + if ($Script:attempts -lt 3) { + & $IRM 'httpstat.us/429' + } + } + + It 'Does not rety if requested not to' { + $Script:attempts = 0 + { Invoke-Method -Context $context -Path '/' -Retry $false } | Should -Throw + Assert-MockCalled Invoke-RestMethod -Exactly 1 -Scope It + } + + It 'Retries by default' { + $Script:attempts = 0 + Invoke-Method -Context $context -Path '/' 3>$null + Assert-MockCalled Invoke-RestMethod -Exactly 3 -Scope It + } + + It 'Retries explicitly' { + $Script:attempts = 0 + Invoke-Method -Context $context -Path '/' -Retry $true 3>$null + Assert-MockCalled Invoke-RestMethod -Exactly 3 -Scope It + } + + Mock -ModuleName PwshZendesk Invoke-RestMethod {} + $Script:Context = $context + + It 'Uses the stored context' { + Invoke-Method -Path '/' + Assert-MockCalled Invoke-RestMethod -Exactly 1 -Scope It + } + } +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..c32ae6a --- /dev/null +++ b/todo.md @@ -0,0 +1,11 @@ + +- Comment Based Help +- Default Parameter Sets +- Tests +- OAuth Support +- Parameter Validation +- OutputType +- Attachment Uploads +- Usage Compatability Check +- PS Edition/Version Compatability Check +- Ticket / User types?