UGDSB.PS.psm1

#Region '.\Public\Add-GraphGroupMember.ps1' 0
function Add-GraphGroupMember{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$groupId,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string[]]$ids
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # Get Graph Headers for Call
  $headers = Get-GraphHeader  
  try{
    $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
    $batches = [System.Collections.Generic.List[PSCustomObject]]@()
    for($i = 1; $i -le $ids.Count; ++$i){
      $obj = [PSCustomObject]@{
        "id" = $i
        "headers" = @{
          "Content-type" = "application/json"
        }
        "method" = "POST"
        "url" = "/groups/$($groupId)/members/`$ref"
        "body" = @{
          "@odata.id" = "https://graph.microsoft.com/beta/directoryObjects/$($ids[$i-1])"
        }
      }
      $batchObj.Add($obj) | Out-Null        
      if($($i % 20) -eq 0){
        $batches.Add($batchObj) | Out-Null
        $batchObj = $null
        $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
      }
    }
    $batches.Add($batchObj) | Out-Null
    foreach($batch in $batches){
      $json = [PSCustomObject]@{
        "requests" = $batch
      } | ConvertTo-JSON -Depth 5
      $results = Invoke-RestMethod -Method "POST" -Uri "https://graph.microsoft.com/beta/`$batch" -Headers $headers -Body $json
    }
    
  } 
  catch{
    throw "Unable to add members. $($_.Exception.Message)"
  }  
}
#EndRegion '.\Public\Add-GraphGroupMember.ps1' 48
#Region '.\Public\Add-GraphIntuneAppAddToESP.ps1' 0
function Add-GraphIntuneAppAddToESP{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'id')][ValidateNotNullOrEmpty()][string]$id,
    [Parameter(Mandatory = $true, ParameterSetName = 'displayName')][ValidateNotNullOrEmpty()][string]$displayName,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$applicationid
  )
  # Body for JSON
  if($id){
    $currentData = Get-GraphIntuneEnrollmentStatusPage -id $id
  }
  elseif($displayName){
    $currentData = Get-GraphIntuneEnrollmentStatusPage -displayName $displayName
  }
  # Endpoint
  $endpoint = "deviceManagement/deviceEnrollmentConfigurations/$($currentData.id)"
  # Add Application to the ESP
  $currentData.selectedMobileAppIds = @("$($currentData.selectedMobileAppIds -join ','),$($applicationid)") -split ","
  $params = @{
    "@odata.type"                             = "#microsoft.graph.windows10EnrollmentCompletionPageConfiguration"
    id                                        = $currentData.id
    displayName                               = $currentData.displayName
    description                               = $currentData.description
    showInstallationProgress                  = $currentData.showInstallationProgress
    blockDeviceSetupRetryByUser               = $currentData.blockDeviceSetupRetryByUser
    allowDeviceResetOnInstallFailure          = $currentData.allowDeviceResetOnInstallFailure
    allowLogCollectionOnInstallFailure        = $currentData.allowLogCollectionOnInstallFailure
    customErrorMessage                        = $currentData.customErrorMessage
    installProgressTimeoutInMinutes           = $currentData.installProgressTimeoutInMinutes
    allowDeviceUseOnInstallFailure            = $currentData.allowDeviceUseOnInstallFailure
    selectedMobileAppIds                      = $currentData.selectedMobileAppIds
    trackInstallProgressForAutopilotOnly      = $currentData.trackInstallProgressForAutopilotOnly
    disableUserStatusTrackingAfterFirstUser   = $currentData.disableUserStatusTrackingAfterFirstUser
    roleScopeTagIds                           = $currentData.roleScopeTagIds
    allowNonBlockingAppInstallation           = $currentData.allowNonBlockingAppInstallation
    installQualityUpdates                     = $currentData.installQualityUpdates
  }
  try{
    $uri = "https://graph.microsoft.com/beta/$($endpoint)"
    $headers = Get-GraphHeader
    Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $($params | ConvertTo-Json -Depth 10) -StatusCodeVariable statusCode
  }
  catch{
    $_
  }
}
#EndRegion '.\Public\Add-GraphIntuneAppAddToESP.ps1' 47
#Region '.\Public\Add-GraphIntuneAppAssignment.ps1' 0
function Add-GraphIntuneAppAssignment{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$applicationid,
    [Parameter(Mandatory = $true)][ValidateSet("available","required","uninstall")][string]$intent,
    [Parameter(Mandatory = $false)][string[]]$groups,
    [Parameter(Mandatory = $false)][Object[]]$filters = $null,
    [Parameter(Mandatory = $false)][switch]$exclude,
    [Parameter(Mandatory = $false)][bool]$foreground = $false,
    [Parameter(Mandatory = $false)][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # Default format for assignment body
  $assignment = [PSCustomObject]@{
    "@odata.type" = "#microsoft.graph.mobileAppAssignment"
    "intent" = $intent
    "target" = $null
    "settings" = $null
  }
  foreach($group in $groups){
    # Set Filter details
    $filterId = $null
    $filterType = "none"
    if($null -ne $filters.$group){
      $assignmentFilters = Get-GraphIntuneFilters
      $filterId = ($assignmentFilters | Where-Object {$_.DisplayName -eq $filters.$group.filterName}).id
      $filterType = $filters.$group.filterType
    }
    $target = [PSCustomObject]@{
      "deviceAndAppManagementAssignmentFilterId" = $filterId
      "deviceAndAppManagementAssignmentFilterType" = $filterType
    }
    # Targeting Values
    switch($group.ToLower()){
      "all users" {
        $target | Add-Member -MemberType "NoteProperty" -Name "@odata.type" -Value "#microsoft.graph.allLicensedUsersAssignmentTarget"
      }
      "all devices" {
        $target | Add-Member -MemberType "NoteProperty" -Name "@odata.type" -Value "#microsoft.graph.allDevicesAssignmentTarget"
      }
      default {
        if($exclude){
          $target | Add-Member -MemberType "NoteProperty" -Name "@odata.type" -Value "#microsoft.graph.exclusionGroupAssignmentTarget"
        }
        else{
          $target | Add-Member -MemberType "NoteProperty" -Name "@odata.type" -Value "#microsoft.graph.groupAssignmentTarget"
        }
        try{
          $groupdetails = Get-GraphGroup -groupName $group
          $target | Add-Member -MemberType "NoteProperty" -Name "groupId" -Value $groupdetails.id
        }
        catch{
          throw "Unable to get ID of the group selected. $($_.Exception.Message)"
        }
      }
    }
    $assignment.target = $target
    # Settings Values
    if(!$exclude){
      $settings = [PSCustomObject]@{
        "@odata.type" = "#microsoft.graph.win32LobAppAssignmentSettings"
      }
      if($foreground){
        $settings | Add-Member -MemberType "NoteProperty" -Name "deliveryOptimizationPriority" -Value "foreground"
      }
      $assignment.settings = $settings
    }
    try{
      $endpoint = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($applicationid)/assignments"
      $headers = Get-GraphHeader
      Invoke-RestMethod -Method post -Uri $endpoint -Headers $headers -Body $($assignment | ConvertTo-Json -Depth 10) -StatusCodeVariable statusCode | Out-Null
    }
    catch{
      if(([REGEX]::Match($((($_ | ConvertFrom-Json).error.message | ConvertFrom-JSON).Message),"The MobileApp Assignment already exists")).Success){
        continue
      }
      else{
        throw $($_.Exception.Message)
      }
    }
  }
}
#EndRegion '.\Public\Add-GraphIntuneAppAssignment.ps1' 82
#Region '.\Public\Add-TopdeskAssetAssignment.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to add an location assignment to an asset
  .PARAMETER assetID
  The unid of the asset we want to update
  .PARAMETER locationID
  The location id that we want to add to asset
  .EXAMPLE
#>

function Add-TopdeskAssetAssignment{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][string]$assetID,
    [Parameter(Mandatory = $true)][string]$locationID   
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }    
  $body = @{
    "assetIds" = @(
      $assetID
    )
    "branchId" = $locationID
    "linkToId" = $locationID
    "linkType" = "branch"
  }
  
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/assetmgmt/assets/assignments"
  try{
    $results = Invoke-RestMethod -Method PUT -Uri $uri -Headers $global:topdeskAccessToken -body ($body | ConvertTo-Json) -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  } 
  return $results
}
#EndRegion '.\Public\Add-TopdeskAssetAssignment.ps1' 38
#Region '.\Public\Add-TopdeskIncidentAttachment.ps1' 0
function Add-TopdeskIncidentAttachment{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$id,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$filepath,
    [Parameter()][bool]$invisibleForCaller = $false
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }  
  $uri = "https://ugdsb-test.topdesk.net/tas/api/incidents/id/$($id)/attachments"
  # Information on file
  $item = Get-Item -Path $filepath
  # Get content of file
  $FileContent = Get-Content $filepath
  # Get bytes of file
  $fileContentInBytes = [System.Text.Encoding]::UTF8.GetBytes($FileContent)
  # Get base 64 of file
  $fileContentEncoded = [System.Convert]::ToBase64String($fileContentInBytes)
  # Build body for upload
  $body = "--BOUNDARY
Content-Disposition: form-data; name=`"file`"; filename=`"$($item.Name)`"
Content-Type: application/plain;charset=utf-8
Content-Transfer-Encoding: base64
 
$($fileContentEncoded)
--BOUNDARY--"

  try{
    $header = $global:topdeskAccessToken.Clone()
    $header."Content-Type" = "multipart/form-data; boundary=BOUNDARY"
    Invoke-RestMethod -Method POST -Uri $uri -Headers $header -body $body -StatusCodeVariable statusCode  
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }   
}
#EndRegion '.\Public\Add-TopdeskIncidentAttachment.ps1' 38
#Region '.\Public\Connect-Meraki.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to convert and store header information for meraki api
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER organizationId
  Your meraki organization ID
#>

function Connect-Meraki{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][SecureString]$credential,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$organizationId,
    [Parameter()][ValidateNotNullOrEmpty()][string]$URI = "https://api.meraki.com/api/v1/"
  )
  # This sets the global variables that are used to connect to the topdesk API
  $global:merakiHeader = @{
    "X-Cisco-Meraki-API-Key" = ConvertFrom-SecureString $credential -AsPlainTex
    "Content-Type" = "application/json"
  }  
  # This sets a variable for the orgId
  $global:merakiOrgId = $organizationId
  # This sets a variable for the orgId
  $global:merakiApiURI = $URI
}
#EndRegion '.\Public\Connect-Meraki.ps1' 26
#Region '.\Public\Connect-Topdesk.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to convert and store header information for topdesk api
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
#>

function Connect-Topdesk{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][pscredential]$credential,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$environment
  )
  # This sets the global variables that are used to connect to the topdesk API
  $global:topdeskAccessToken = @{
    "Authorization" = "Basic $([System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$($credential.username):$($credential.GetNetworkCredential().password)")))"
    "Content-Type" = "application/json"
  }  
  # This sets a variable for the enviorment
  $global:topdeskEnvironment = $environment
}
#EndRegion '.\Public\Connect-Topdesk.ps1' 23
#Region '.\Public\Convert-EntraObjIDtoSid.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will convert a Azure AD Object ID TO Sid
  .PARAMETER ObjectId
  Azure AD Object ID
  .EXAMPLE
  Convert AzADObject to Sid
    Convert-EntraObjIDtoSid -objectId <ID>
#>

function Convert-EntraObjIDtoSid{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$ObjectId
  )  
  $bytes = [Guid]::Parse($ObjectId).ToByteArray()
  $array = New-Object 'UInt32[]' 4
  [Buffer]::BlockCopy($bytes, 0, $array, 0, 16)
  $sid = "S-1-12-1-$array".Replace(' ', '-')
  return $sid  
}
#EndRegion '.\Public\Convert-EntraObjIDtoSid.ps1' 21
#Region '.\Public\Convert-SidtoEntraObjID.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will convert a SID to an Azure AD Object ID
  .PARAMETER sid
  SID of object
  .EXAMPLE
  Convert Sid to Object ID
    Convert-SidtoEntraObjID -sid <SID>
#>

function Convert-SidtoEntraObjID{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$sid
  )
  $text = $sid.Replace('S-1-12-1-', '')
  $array = [UInt32[]]$text.Split('-')
  $bytes = New-Object 'Byte[]' 16
  [Buffer]::BlockCopy($array, 0, $bytes, 0, 16)
  [Guid]$guid = $bytes
  return $guid        
}
#EndRegion '.\Public\Convert-SidtoEntraObjID.ps1' 22
#Region '.\Public\Copy-GraphIntuneAppAssignments.ps1' 0
function Copy-GraphIntuneAppAssignments{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$applicationid,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$copyapplicationid
  )
  # Get the Assignments that we will be copying
  $assignments = Get-GraphIntuneAppAssignment -applicationid $copyapplicationid
  # Loop through the assignments
  foreach($assignment in $assignments){
    $assignment = [PSCustomObject]@{
      "@odata.type" = "#microsoft.graph.mobileAppAssignment"
      "intent" = $assignment.intent
      "target" = $assignment.target
      "settings" = $assignment.settings
    }
    try{
      $endpoint = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($applicationid)/assignments"
      $headers = Get-GraphHeader
      Invoke-RestMethod -Method post -Uri $endpoint -Headers $headers -Body $($assignment | ConvertTo-Json -Depth 10) -StatusCodeVariable statusCode | Out-Null
    }
    catch{
      if(([REGEX]::Match($((($_ | ConvertFrom-Json).error.message | ConvertFrom-JSON).Message),"The MobileApp Assignment already exists")).Success){
        continue
      }
      else{
        throw $($_.Exception.Message)
      }
    }
  }
}
#EndRegion '.\Public\Copy-GraphIntuneAppAssignments.ps1' 32
#Region '.\Public\Disable-Chromebook.ps1' 0
function Disable-Chromebook{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][string]$deviceID
  )
  # Ensure that they have a access token
  if(-not $global:googleAccessToken){
    throw "Please ensure that you have called Get-GoogleAccessToken cmdlet"
  }  
  # Confirm we have a valid access token
  if(-not $(Test-GoogleAccessToken)){
    Get-GoogleAccessToken -json $global:googleJSON -customerId $global:googleCustomerId
  }
  # Generate URI for REST API call
  $uri = "https://admin.googleapis.com/admin/directory/v1/customer/$($global:googleCustomerId)/devices/chromeos/$($deviceID)/action"
  # REST API Call to Get Orginzation Lists
  $headers = @{
    authorization = "Bearer $($Global:googleAccessToken)"
    "content-type" = "application/json"
  }
  $body = @{
    "action" = "disable"
  }
  $result = Invoke-RestMethod -Method "POST" -URI $uri -Headers $headers -Body ($body | ConvertTo-Json) -StatusCodeVariable statusCode
  Write-Verbose "Status Result: $($statusCode)"

}
#EndRegion '.\Public\Disable-Chromebook.ps1' 28
#Region '.\Public\Enable-Chromebook.ps1' 0
function Enable-Chromebook{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][string]$deviceID
  )
  # Ensure that they have a access token
  if(-not $global:googleAccessToken){
    throw "Please ensure that you have called Get-GoogleAccessToken cmdlet"
  }  
  # Confirm we have a valid access token
  if(-not $(Test-GoogleAccessToken)){
    Get-GoogleAccessToken -json $global:googleJSON -customerId $global:googleCustomerId
  }
  # Generate URI for REST API call
  $uri = "https://admin.googleapis.com/admin/directory/v1/customer/$($global:googleCustomerId)/devices/chromeos/$($deviceID)/action"
  # REST API Call to Get Orginzation Lists
  $headers = @{
    authorization = "Bearer $($Global:googleAccessToken)"
    "content-type" = "application/json"
  }
  $body = @{
    "action" = "reenable"
  }
  $result = Invoke-RestMethod -Method "POST" -URI $uri -Headers $headers -Body ($body | ConvertTo-Json) -StatusCodeVariable statusCode
  Write-Verbose "Status Result: $($statusCode)"

}
#EndRegion '.\Public\Enable-Chromebook.ps1' 28
#Region '.\Public\Get-AlertFactoryTicketDetails.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to parse out data from emails for Alert Factory
#>

function Get-AlertFactoryTicketDetails{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$details,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$emails
  )
  # Generate blank hashtable for ticket
  $ticketDetails = @{}
  foreach($obj in $details.PSObject.Properties){
    if($null -ne $obj.value.type){
      if($null -ne $obj.value.attribute){
        switch($obj.value.attribute){
          "createdDateTime" {
            $content = $emails[0].createdDateTime
          }
          "receivedDateTime" {
            $content = $emails[0].receivedDateTime
          }
          "sentDateTime" {
            $content = $emails[0].sentDateTime
          }
          "subject" {
            $content = $emails[0].subject
          }
          "bodyPreview"{
            $content = $emails[0].bodyPreview
          }
          "body" {
            $content = $emails[0].body.content
          }
          "sender" {
            $content = $emails[0].sender.emailAddress.address
          }
          "from" {
            $content = $emails[0].from.emailAddress.address
          }
        }
      }
      else{
        $content = $obj.value.value
      }
      switch($obj.value.type){
        "source" {
          $ticketDetails.Add($obj.Name,$content) | Out-Null
        }
        "string" {
          $ticketDetails.Add($obj.Name,$obj.value.value) | Out-Null
        }
        "regex" {
          $match = [Regex]::Match($content,$obj.value.value)
          $val = $null          
          if($match.Success){
            $val = $match.Value
          }
          $ticketDetails.Add($obj.Name,$val) | Out-Null
        }
      }
    }
  }
  return $ticketDetails
}
#EndRegion '.\Public\Get-AlertFactoryTicketDetails.ps1' 66
#Region '.\Public\Get-ChromeDevices.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will retrive chrome OS devices
  https://developers.google.com/admin-sdk/directory/reference/rest/v1/chromeosdevices/list
  https://developers.google.com/admin-sdk/directory/v1/list-query-operators
  .PARAMETER maxResults
  How many results to return per page, maximum per page is 300
  .PARAMETER orderBy
  How the results should be sorted
  .PARAMETER orgUnitPath
  Restrict to a specific organization unit
  .PARAMETER projection
  If we want basic or full data. Default is full.
  .PARAMETER query
  The query to use against the data
  https://developers.google.com/admin-sdk/directory/v1/list-query-operators
  .PARAMETER sortOrder
  Ascending or Descending sort order
  .PARAMETER includeChildOrgunits
  If we should include child organization in use with orgUnitPath
  .PARAMETER all
  If we should return all results and not just a single page
#>

function Get-ChromeDevices{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter()][int]$maxResults,
    [Parameter()][ValidateSet('ANNOTATED_LOCATION','ANNOTATED_USER','LAST_SYNC','NOTES','SERIAL_NUMBER','STATUS')][string]$orderBy,
    [Parameter()][string]$orgUnitPath,
    [Parameter()][ValidateSet('BASIC','FULL')][string]$projection,
    [Parameter()][string]$query,
    [Parameter()][ValidateSet('ASCENDING','DESCENDING')][string]$sortOrder,
    [Parameter()][switch]$includeChildOrgunits,
    [Parameter()][switch]$all
  )
  # Ensure that they have a access token
  if(-not $global:googleAccessToken){
    throw "Please ensure that you have called Get-GoogleAccessToken cmdlet"
  }  
  # Confirm we have a valid access token
  if(-not $(Test-GoogleAccessToken)){
    Get-GoogleAccessToken -json $global:googleJSON -customerId $global:googleCustomerId
  }
  # Generate URI for REST API call
  $uri = "https://admin.googleapis.com/admin/directory/v1/customer/$($global:googleCustomerId)/devices/chromeos"
  $options = [System.Collections.Generic.List[String]]@()
  if($maxResults){
    $options.Add("maxResults=$($maxResults)") | Out-Null
  }  
  if($orderBy){
    $options.Add("orderBy=$($orderBy)") | Out-Null
  }    
  if($orgUnitPath){
    $options.Add("orgUnitPath=$($orgUnitPath)") | Out-Null
  }     
  if($projection){
    $options.Add("projection=$($projection)") | Out-Null
  }    
  if($sortOrder){
    $options.Add("sortOrder=$($sortOrder)") | Out-Null
  }     
  if($includeChildOrgunits){
    $options.Add("includeChildOrgunits=$($includeChildOrgunits)") | Out-Null
  }    
  if($query){
    $options.Add("query=$($query)") | Out-Null
  }   
  if($options.count -gt 0){
    $uri = "$($uri)?$($options -join "&")"
  }   

  # REST API Call to Get Orginzation Lists
  $splat = @{
    Method = "GET"
    Headers = @{authorization = "Bearer $($Global:googleAccessToken)"}
  }
  $baseURI = $uri
  $deviceList = [System.Collections.Generic.List[PSCustomObject]]@()
  do{
    Write-Verbose "$($baseURI)"
    Write-Verbose "Count: $($deviceList.count)"
    $result = Invoke-RestMethod @splat -uri $baseURI
    foreach($item in $result.chromeosdevices){
      $deviceList.add($item) | Out-Null
    }
    if($options.count -eq 0){$baseURI = "$($uri)?"}
    else{$baseURI = "$($uri)&"}
    $baseURI = "$($baseURI)pageToken=$($result.nextPageToken)"
  }while($null -ne $result.nextPageToken -and $all)
  return $deviceList
}
#EndRegion '.\Public\Get-ChromeDevices.ps1' 93
#Region '.\Public\Get-GoogleAccessToken{.ps1' 0
function Get-GoogleAccessToken{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][PSCustomObject]$json,
    [Parameter(Mandatory = $true)][string]$customerId
  )
  if($Global:googleAccessToken){
    if(Test-GoogleAccessToken){return}
  }
  # Convert JSOn to object
  $json = $json | ConvertFrom-Json 
  # COvert Private Key to Byte Stream
  $rsaPrivateKey = [System.Text.Encoding]::UTF8.GetBytes($json.private_key)  
  # List of Scopes for the token
  $scopes = @(
    "https://www.googleapis.com/auth/admin.directory.device.chromeos",
    "https://www.googleapis.com/auth/admin.directory.group.member",
    "https://www.googleapis.com/auth/admin.directory.orgunit",
    "https://www.googleapis.com/auth/admin.directory.user",
    "https://www.googleapis.com/auth/admin.directory.user.security",
    "https://www.googleapis.com/auth/admin.directory.rolemanagement",
    "https://www.googleapis.com/auth/admin.directory.userschema",
    "https://www.googleapis.com/auth/admin.directory.customer",
    "https://www.googleapis.com/auth/admin.directory.domain",
    "https://www.googleapis.com/auth/admin.directory.resource.calendar",
    "https://mail.google.com/",
    "http://sites.google.com/feeds",
    "https://www.googleapis.com/auth/apps.alerts",
    "https://www.googleapis.com/auth/calendar",
    "https://www.googleapis.com/auth/classroom.announcements",
    "https://www.googleapis.com/auth/classroom.coursework.students",
    "https://www.googleapis.com/auth/classroom.courseworkmaterials",
    "https://www.googleapis.com/auth/classroom.profile.emails",
    "https://www.googleapis.com/auth/classroom.rosters",
    "https://www.googleapis.com/auth/classroom.topics",
    "https://www.googleapis.com/auth/cloud-identity",
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/contacts",
    "https://www.googleapis.com/auth/datastudio",
    "https://www.googleapis.com/auth/documents",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/drive.activity",
    "https://www.googleapis.com/auth/drive.admin.labels",
    "https://www.googleapis.com/auth/drive.labels",
    "https://www.googleapis.com/auth/gmail.modify",
    "https://www.googleapis.com/auth/gmail.settings.basic",
    "https://www.googleapis.com/auth/gmail.settings.sharing",
    "https://www.googleapis.com/auth/keep",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/tasks",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/admin.directory.device.mobile"
  )  
  # Get Current Time
  $now = (Get-Date).ToUniversalTime()
  # Expiry Time
  $expiry = (Get-Date).ToUniversalTime().AddHours(1)
  # Convert to Format for JWT
  $createDate = [Math]::Floor([decimal](Get-Date($now) -UFormat "%s"))
  $expiryDate = [Math]::Floor([decimal](Get-Date($expiry) -UFormat "%s"))   
  # Create JWT Payload
  $jwtPayload = @{
    sub = $json.client_email
    scope = $($scopes -join " ")
    aud = "https://oauth2.googleapis.com/token"
    iat = $createDate
  }
  $jwt = New-JWT -Algorithm 'RS256' -Issuer $json.client_email -SecretKey $rsaPrivateKey -ExpiryTimestamp $expiryDate -PayloadClaims $jwtPayload 
  # Request Google API Token
  $tokenVars = @{
    Method = "POST"
    Uri =  "https://oauth2.googleapis.com/token"
    ContentType = "application/x-www-form-urlencoded"
    Body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=$jwt"
  }
  $token = Invoke-WebRequest @tokenVars
  $Global:googleAccessToken = ($token.content | ConvertFrom-JSON).access_token
  $global:googleJSON = $json
  $global:googleCustomerId = $customerId
}
#EndRegion '.\Public\Get-GoogleAccessToken{.ps1' 82
#Region '.\Public\Get-GoogleOU.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will use GAM installed on your system to query Google for the list of your OUs.
  https://developers.google.com/admin-sdk/directory/reference/rest/v1/orgunits/list
  .PARAMETER Type
  The type of organization units to return.
  .PARAMETER orgUnitPath
  The starting point for the OU table
#>

function Get-GoogleOU{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter()][ValidateSet('All','Children','All_Including_Parent')][string]$Type,
    [Parameter()][string]$orgUnitPath
  )
  # Ensure that they have a access token
  if(-not $global:googleAccessToken){
    throw "Please ensure that you have called Get-GoogleAccessToken cmdlet"
  }  
  # Confirm we have a valid access token
  if(-not $(Test-GoogleAccessToken)){
    Get-GoogleAccessToken -json $global:googleJSON -customerId $global:googleCustomerId
  }
  # Generate URI for REST API call
  $uri = "https://admin.googleapis.com/admin/directory/v1/customer/$($global:googleCustomerId)/orgunits"
  $options = [System.Collections.Generic.List[String]]@()
  if($Type){
    $options.Add("type=$($type)") | Out-Null
  }
  if($orgUnitPath){
    $options.Add("orgUnitPath=$($orgUnitPath)") | Out-Null
  }
  if($options.count -gt 0){
    $uri = "$($uri)?$($options -join "&")"
  }  
  # REST API Call to Get Orginzation Lists
  $splat = @{
    Method = "GET"
    Uri = $uri
    Headers = @{authorization = "Bearer $($Global:googleAccessToken)"}
  }
  $orglist = Invoke-RestMethod @splat
  # Return the list
  return $orglist.organizationUnits
}
#EndRegion '.\Public\Get-GoogleOU.ps1' 47
#Region '.\Public\Get-GraphAccessToken.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to use MSAL.PS to get an access token through an app registration, and then store in a global access token variable
  .PARAMETER clientID
  The client ID for the app registration used
  .PARAMETER clientSecret
  The secret used for the app registration
  .PARAMETER tenantID
  The tenant id for the app registration
#>

function Get-GraphAccessToken{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$clientID,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][SecureString]$clientSecret,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$tenantID
  )
  if($(Test-GraphAcessToken $global:graphAccessToken)){
    return $global:graphAccessToken
  }
  $accessTokenVars = @{
    "clientID" = $clientID
    "clientSecret" = $clientSecret
    "tenantID" = $tenantID
    "ForceRefresh" = $true
  }
  $global:graphAccessToken = Get-MsalToken @accessTokenVars
}
#EndRegion '.\Public\Get-GraphAccessToken.ps1' 29
#Region '.\Public\Get-GraphDevice.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get devices from Azure ID
#>

function Get-GraphDevice{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string[]]$id,
    [Parameter()][ValidateNotNullOrEmpty()][string[]]$deviceId,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$accountEnabled,
    [Parameter()][ValidateNotNullOrEmpty()][string]$displayName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$fields
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # Create empty list
  $filters =  [System.Collections.Generic.List[PSCustomObject]]@()  
  if($displayName){
    $filters.Add("displayName eq '$($displayName)'") | Out-Null
  }
  if($accountEnabled){
    $filters.Add("accountEnabled eq '$($accountEnabled)'") | Out-Null
  }
  if($deviceId.count -eq 1){  
    $filters.Add("deviceId eq '$($deviceId)'") | Out-Null
  }
  $batch = $false
  # General endpoint
  $endpoint = "devices"
  if($id.count -eq 1){
    $endpoint = "$($endpoint)/$($id)"
  }
  elseif($id.count -gt 1 -or $deviceId.count -gt 1){
    $batch = $true
  }
  # Create query string for the filter
  $filterList = $filters -join " and "
  # Create empty list
  $deviceList =  [System.Collections.Generic.List[PSCustomObject]]@() 
  # Get Graph Headers for Call
  $headers = Get-GraphHeader       
  # If ID and deviceID are not an array
  if(-not $batch){
    # Setup endpoint based on if filter or fields are passed
    if($filterList){
      $endpoint = "$($endpoint)?`$filter=$($filterList)"
    }
    if($filterList -and $fields){
      $endpoint = "$($endpoint)&`$select=$($fields)"
    }
    elseif($fields){
      $endpoint = "$($endpoint)?`$select=$($fields)"
    }    
    # Try to call graph API and get results back
    try{
      $uri = "https://graph.microsoft.com/beta/$($endpoint)"
      do{
        $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
        if($results.value){
          foreach($item in $results.value){
            $deviceList.Add($item) | Out-Null
          }
        }
        else{
          $deviceList.Add($results) | Out-Null
        }
        $uri = $results."@odata.nextLink"
      }while($null -ne $results."@odata.nextLink")      
    }
    catch{
      throw "Unable to get devices. $($_.Exception.Message)"
    } 
    return $deviceList   
  }
  # If a batch job because id or device id is an array
  else{
    $objid = 1
    $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
    $batches = [System.Collections.Generic.List[PSCustomObject]]@()
    # if trying to return multiple ids
    if($id.count -gt 1){
      foreach($device in $id){
        if($objid -lt 21){
          if($fields){
            $uri = "$($endpoint)/$($device)?`$select=$($fields)"
          }
          else{
            $uri = "$($endpoint)/$($device)"
          }
          $obj = [PSCustomObject]@{
            "id" = $objid
            "method" = "GET"
            "url" = $uri
          }
          $batchObj.Add($obj) | Out-Null
          $objid++          
        }
        if($objId -eq 21){
          $batches.Add($batchObj) | Out-Null
          $batchObj = $null
          $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
          $objid = 1 
        }        
      }
      $batches.Add($batchObj) | Out-Null
    }
    # if trying to return multiple deviceids
    elseif($deviceId.Count -gt 1){
      foreach($device in $deviceId){
        if($objid -lt 21){
          if($fields){
            $uri = "$($endpoint)?`$filter=deviceId eq '$($device)'&`$select=$($fields)"
          }
          else{
            $uri = "$($endpoint)?`$filter=deviceId eq '$($device)'"
          }
          $obj = [PSCustomObject]@{
            "id" = $objid
            "method" = "GET"
            "url" = $uri
          }
          $batchObj.Add($obj) | Out-Null
          $objid++          
        }
        if($objId -eq 21){
          $batches.Add($batchObj) | Out-Null
          $batchObj = $null
          $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
          $objid = 1 
        }        
      }
      $batches.Add($batchObj) | Out-Null
    }
    for($x = 0; $x -lt $batches.count; $x++){
      if($batches[$x].count -gt 0){
        $json = [PSCustomObject]@{
          "requests" = $batches[$x] 
        } | ConvertTo-JSON    
        $results = Invoke-RestMethod -Method "POST" -Uri "https://graph.microsoft.com/beta/`$batch" -Headers $headers -Body $json
        foreach($item in $results.responses.body){
          if($item.value){
            $deviceList.Add($item.value) | Out-Null
          }
          else{
            $deviceList.Add($item) | Out-Null
          }

        }
      }    
    }
    return $deviceList
  }
}
#EndRegion '.\Public\Get-GraphDevice.ps1' 155
#Region '.\Public\Get-GraphGroup.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to query graph for Entra ID groups
  .PARAMETER groupName
  If want to find based on group name
  .PARAMETER groupId
  If want to lookup by group id
  .PARAMETER All
  If want to return all groups
#>

function Get-GraphGroup{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'groupName')][ValidateNotNullOrEmpty()][string]$groupName,
    [Parameter(Mandatory = $true, ParameterSetName = 'groupId')][ValidateNotNullOrEmpty()][string]$groupId,
    [Parameter(Mandatory = $true, ParameterSetName = 'All')][switch]$All
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  # Create empty list
  $groupList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Build the headers we will use to get groups
  $headers = Get-GraphHeader
  # Base URI for resource call
  $uri = "https://graph.microsoft.com/beta/groups"
  if($groupName){
    # Filter based on group name is required
    $uri = "$($uri)?`$filter=displayName eq '$($groupName)'"
  }
  elseif($groupId){
    # Filter based on group ID
    $uri = "$($uri)/$($groupId)"
  }
  try{
    # Loop until nextlink is null
    do{
      # Execute call against graph
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      # Add results to a list variable
      foreach($item in $results.value){
        $groupList.Add($item) | Out-Null
      }
      # Set the URI to the nextlink if it exists
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
    # If there is only one result, return that
    if($groupList.count -eq 0){
      return $results
    }
    else{
      # Return the group list if it exists
      return $groupList
    }
  }
  catch{
    throw "Unable to get groups. $($_.Exception.Message)"
  }
}
#EndRegion '.\Public\Get-GraphGroup.ps1' 61
#Region '.\Public\Get-GraphGroupMembers.ps1' 0
#https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-beta&tabs=http
function Get-GraphGroupMembers{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'groupName')][ValidateNotNullOrEmpty()][string]$groupName,
    [Parameter(Mandatory = $true, ParameterSetName = 'groupId')][ValidateNotNullOrEmpty()][string]$groupId,
    [Parameter()][switch]$Recurse
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  # Get the Group ID if Group Name was Sent
  if($groupName){
    $group = Get-GraphGroup -groupName $groupName
    $groupId = $group.id
  }
  # Create empty list
  $groupMemberList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Create List for recurse if needed
  $groupList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Build the headers we will use to get groups
  $headers = Get-GraphHeader
  # Deterime if we just want members of transitive members
  if($Recurse){
    $uri = "https://graph.microsoft.com/beta/groups/$($groupId)/transitiveMembers"
  }
  else{
    $uri = "https://graph.microsoft.com/beta/groups/$($groupId)/members"
  }
  try{
    # Loop until nextlink is null
    do{
      # Execute call against graph
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      # Add results to a list variable
      foreach($item in $results.value){
        $groupMemberList.Add($item)
      }
      # Set the URI to the nextlink if it exists
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
    return $groupMemberList
  }
  catch{
    throw "Unable to get group members. $($_.Exception.Message)"
  }
}
#EndRegion '.\Public\Get-GraphGroupMembers.ps1' 49
#Region '.\Public\Get-GraphHeader.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to format the graph header for the REST api calls
  .PARAMETER ConsistencyLevel
  This field will add the ConsistencyLevel variable to eventual
#>

function Get-GraphHeader{
  [CmdletBinding()]
  param(
    [Parameter()][switch]$ConsistencyLevel
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  # Base header variables
  $headerVars = @{
    Authorization = "Bearer $($global:graphAccessToken.AccessToken)"
    "Content-Type" = "application/json"
  }
  # If flagged to include the Consitency Level header
  if($ConsistencyLevel.IsPresent){
    $headerVars.Add("ConsistencyLevel","eventual")
  }
  return $headerVars
}
#EndRegion '.\Public\Get-GraphHeader.ps1' 26
#Region '.\Public\Get-GraphIntuneApp.ps1' 0
function Get-GraphIntuneApp{
  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'id')][ValidateNotNullOrEmpty()][string]$id,
    [Parameter(Mandatory = $true, ParameterSetName = 'displayName')][ValidateNotNullOrEmpty()][string]$displayName,
    [Parameter()][ValidateNotNullOrEmpty()][ValidateSet("microsoft.graph.androidManagedStoreApp","microsoft.graph.iosStoreApp","microsoft.graph.iosVppApp","microsoft.graph.macOSLobApp","microsoft.graph.macOSMicrosoftEdgeApp","microsoft.graph.macOSOfficeSuiteApp","microsoft.graph.macOSPkgApp","microsoft.graph.macOsVppApp","microsoft.graph.managedAndroidStoreApp","microsoft.graph.managedIOSStoreApp","microsoft.graph.officeSuiteApp","microsoft.graph.webApp","microsoft.graph.win32LobApp","microsoft.graph.winGetApp")]
      [string]$type,
    [Parameter()][ValidateNotNullOrEmpty()][string]$fields
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  
  
  # URI Endpoint
  $endpoint = "deviceAppManagement/mobileApps"
  # Build filters for URI
  $filters =  [System.Collections.Generic.List[PSCustomObject]]@()
  if($id){
    $filters.Add("id eq '$($id)'") | Out-Null
  }
  if($displayName){
    $filters.Add("displayName eq '$($displayName)'") | Out-Null
  }  
  if($type){
    $filters.Add("(isof('$($type)'))") | Out-Null
  }   
  # Create query string for the filter
  $filterList = $filters -join " and "
  if($filterList){
    $endpoint = "$($endpoint)?`$filter=$($filterList)"
  }
  if($filterList -and $fields){
    $endpoint = "$($endpoint)&`$select=$($fields)"
  }
  elseif($fields){
    $endpoint = "$($endpoint)?`$select=$($fields)"
  }
  # Create empty list
  $applicationList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Get Graph Headers for Call
  $headers = Get-GraphHeader
  try{
    $uri = "https://graph.microsoft.com/beta/$($endpoint)"
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      if($results.value){
        foreach($item in $results.value){
          $applicationList.add($item)
        }
      }
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
  }
  catch{
    throw "Unable to get devices. $($_.Exception.Message)"
  }  
  return $applicationList
}
#EndRegion '.\Public\Get-GraphIntuneApp.ps1' 60
#Region '.\Public\Get-GraphIntuneAppAssignment.ps1' 0
function Get-GraphIntuneAppAssignment{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$applicationid
  )
  $endpoint = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($applicationid)/assignments"
  $headers = Get-GraphHeader
  $results = Invoke-RestMethod -Method "GET" -Uri $endpoint -Headers $headers
  return $results.value
}
#EndRegion '.\Public\Get-GraphIntuneAppAssignment.ps1' 11
#Region '.\Public\Get-GraphIntuneEnrollmentStatusPage.ps1' 0
function Get-GraphIntuneEnrollmentStatusPage{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$displayName
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  # URI Endpoint
  $endpoint = "deviceManagement/deviceEnrollmentConfigurations"
  if($id){
    $endpoint = "$($endpoint)/$($id)"
  }
  if($displayName){
    $endpoint = "$($endpoint)?`$filter=displayName eq '$($displayName)'"
  }
  # Create empty list
  $esplist =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Get Graph Headers for Call
  $headers = Get-GraphHeader
  try{
    $uri = "https://graph.microsoft.com/beta/$($endpoint)"
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      if($results.value){
        foreach($item in $results.value){
          $esplist.add($item)
        }
      }
      else{
        $esplist.add($results)
      }
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
  }
  catch{
    throw "Unable to get devices. $($_.Exception.Message)"
  }
  return $esplist  
}
#EndRegion '.\Public\Get-GraphIntuneEnrollmentStatusPage.ps1' 42
#Region '.\Public\Get-GraphIntuneFilters.ps1' 0
function Get-GraphIntuneFilters{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$displayName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$platform
  )
  # Construct array list to build the dynamic filter list
  $FilterList = [System.Collections.Generic.List[PSCustomObject]]@()
  if($id){
    $FilterList.Add("`$PSItem.id -eq '$($id)'") | Out-Null
  }
  if($displayName){
    $FilterList.Add("`$PSItem.displayName -eq '$($displayName)'") | Out-Null
  }
  if($platform){
    $FilterList.Add("`$PSItem.platform -eq '$($platform)'") | Out-Null
  }
  # Construct script block from filter list array
  $FilterExpression = [scriptblock]::Create(($FilterList -join " -and ")) 
  # Create empty list
  $filters =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Build the headers we will use to get groups
  $headers = Get-GraphHeader  
  # Endpoint for the API
  $uri = "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters"
  try{
    # Loop until nextlink is null
    do{
      # Execute call against graph
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      # Add results to a list variable
      foreach($item in $results.value){
        $filters.Add($item)
      }
      # Set the URI to the nextlink if it exists
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
    if($FilterList.Count -gt 0){
      return $filters | Where-Object -FilterScript $FilterExpression
    }
    return $filters
  }
  catch{
    throw "Unable to get group members. $($_.Exception.Message)"
  }  
}
#EndRegion '.\Public\Get-GraphIntuneFilters.ps1' 48
#Region '.\Public\Get-GraphMail.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to read email from a specific mailbox
  .PARAMETER emailAddress
  The email address of the account that we are reading from
  .PARAMETER folder
  The ID of the folder that we want to read from, if it is not the whole mailbox
  .PARAMETER unread
  If we want to return only unread email
#>

function Get-GraphMail{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$emailAddress,
    [Parameter()][ValidateNotNullOrEmpty()][string]$folder,
    [Parameter()][switch]$unread
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  $headers = Get-GraphHeader
  $uri = "https://graph.microsoft.com/beta/users/$($emailAddress)/messages"
  $FilterList = [System.Collections.Generic.List[PSCustomObject]]@()
  if ($folder) {
    $FilterList.Add("parentFolderId eq '$($folderInID)'") | Out-Null
  }  
  if ($unread) {
    $FilterList.Add("isRead eq false") | Out-Null
  } 
  if($FilterList -ne "")
  {
    $FilterExpression = [scriptblock]::Create(($FilterList -join " and "))
    $uri = "$($uri)?`$filter=$($FilterExpression)"
  }
  try{
    $emailList =  [System.Collections.Generic.List[PSCustomObject]]@()
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      foreach($item in $results.value){
        $emailList.Add($item) | Out-Null
      }    
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
    # If there is only one result, return that
    if($emailList.count -eq 0){
      return $results
    }
    else{
      # Return the group list if it exists
      return $emailList
    }
  }
  catch{
    throw "Unable to get emails from mailbox."
  }
}
#EndRegion '.\Public\Get-GraphMail.ps1' 57
#Region '.\Public\Get-GraphManagedDevice.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get managed devices (intune) from the graph endpoints
#>

function Get-GraphManagedDevice{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$lastSyncBefore,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$lastSyncAfter,
    [Parameter()][ValidateNotNullOrEmpty()][ValidateSet("Windows","Android","macOS","iOS")][string]$operatingSystem,
    [Parameter()][ValidateNotNullOrEmpty()][ValidateSet("compliant","noncompliant","unknown")][string]$complianceState,
    [Parameter()][ValidateNotNullOrEmpty()][string]$OSVersion,
    [Parameter()][ValidateNotNullOrEmpty()][string]$OSVersionStartsWith,
    [Parameter()][ValidateNotNullOrEmpty()][string]$id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$azureADDeviceId,
    [Parameter()][ValidateNotNullOrEmpty()][string]$userPrincipalName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$model,
    [Parameter()][ValidateNotNullOrEmpty()][string]$manufacturer,
    [Parameter()][ValidateNotNullOrEmpty()][string]$serialNumber,
    [Parameter()][ValidateNotNullOrEmpty()][ValidateSet("disabled","enabled")][string]$lostModeState,
    [Parameter()][ValidateNotNullOrEmpty()][string]$minimumOSVersion,
    [Parameter()][ValidateNotNullOrEmpty()][string]$maximumOSVersion,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$isEncrypted,
    [Parameter()][ValidateNotNullOrEmpty()][string]$fields,
    [Parameter()][switch]$batch
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }   
  # Create Filters for the URI
  # Create empty list
  $filters =  [System.Collections.Generic.List[PSCustomObject]]@()  
  # Build Filters
  if($lastSyncBefore){
    $filters.Add("lastSyncDateTime le $($lastSyncBefore.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))") | Out-Null
  }
  if($lastSyncAfter){
    $filters.Add("lastSyncDateTime ge $($lastSyncAfter.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))") | Out-Null
  }  
  if($operatingSystem){
    $filters.Add("operatingSystem eq '$($operatingSystem)'") | Out-Null
  }   
  if($complianceState){
    $filters.Add("complianceState eq '$($complianceState)'") | Out-Null
  }     
  if($OSVersion){
    $filters.Add("OSVersion eq '$($OSVersion)'") | Out-Null
  }    
  if($OSVersionStartsWith){
    $filters.Add("startsWith(OSVersion,'$($OSVersionStartsWith)')") | Out-Null
  }  
  if($azureADDeviceId){
    $filters.Add("azureADDeviceId eq '$($azureADDeviceId)'") | Out-Null
  }   
  if($userPrincipalName){
    $filters.Add("userPrincipalName eq '$($userPrincipalName)'") | Out-Null
  } 
  if($model){
    $filters.Add("model eq '$($model)'") | Out-Null
  }          
  if($manufacturer){
    $filters.Add("manufacturer eq '$($manufacturer)'") | Out-Null
  }    
  if($serialNumber){
    $filters.Add("serialNumber eq '$($serialNumber)'") | Out-Null
  }   
  # Create query string for the filter
  $filterList = $filters -join " and "
  # URI Endpoint
  $endpoint = "deviceManagement/managedDevices"
  if($id){
    $uri = "$($endpoint)/$($id)"
  } 
  else{
    $uri = "$($endpoint)"    
  }  
  if($filterList){
    $uri = "$($uri)?`$filter=$($filterList)"
  }
  if(!$batch){
    if($fields -ne ""){
      if($filters.count -gt 0){
        $uri = "$($uri)&`$select=$($fields)"
      }
      else{
        $uri = "$($uri)?`$select=$($fields)"
      }
    }
  }
  else{
    if($fields -ne ""){
      if($filters.count -gt 0){
        $uri = "$($uri)&`$select=id"
      }
      else{
        $uri = "$($uri)?`$select=id"
      }    
    }
  }
  # Create empty list
  $deviceList =  [System.Collections.Generic.List[PSCustomObject]]@()  
  # Create empty list
  $idlist =  [System.Collections.Generic.List[PSCustomObject]]@()    
  # Get Graph Headers for Call
  $headers = Get-GraphHeader  
  try{
    $uri = "https://graph.microsoft.com/beta/$($uri)"
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      if($results.value){
        foreach($item in $results.value){
          if(!$batch){$deviceList.Add($item) | Out-Null}
          else{$idlist.Add($item) | Out-Null}
        }
      }
      else{
        if(!$batch){$deviceList.Add($item) | Out-Null}
        else{$idlist.Add($item) | Out-Nul}
      }
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
  }
  catch{
    throw "Unable to get devices. $($_.Exception.Message)"
  }
  if(!$batch)
  {
    return $deviceList
  }
  $objid = 1
  $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
  $batches = [System.Collections.Generic.List[PSCustomObject]]@()
  foreach($device in $idlist){
    if($objid -lt 21){
      $url = "deviceManagement/managedDevices/$($device.id)"
      if($fields -ne ""){
        $url = "$($url)?`$select=$($fields)"
      }
      $obj = [PSCustomObject]@{
        "id" = $objid
        "method" = "GET"
        "url" = $url
      }
      $batchObj.Add($obj) | Out-Null
      $objid++
    }
    if($objId -eq 21){
      $batches.Add($batchObj) | Out-Null
      $batchObj = $null
      $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
      $objid = 1 
    }
  }  
  $batches.Add($batchObj) | Out-Null
  for($x = 0; $x -lt $batches.count; $x++){
    if($batches[$x].count -gt 0){
      $json = [PSCustomObject]@{
        "requests" = $batches[$x] 
      } | ConvertTo-JSON    
      $results = Invoke-RestMethod -Method "POST" -Uri "https://graph.microsoft.com/beta/`$batch" -Headers $headers -Body $json
      foreach($item in $results.responses.body){
        $deviceList.Add($item) | Out-Null
      }   
    } 
  }
  return $deviceList
}
#EndRegion '.\Public\Get-GraphManagedDevice.ps1' 168
#Region '.\Public\Get-GraphSignInAuditLogs.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to query the sign in logs for the users in the entra id tenant
  .PARAMETER userDisplayName
  The list of user display names that we should be looking for
  .PARAMETER userPrincipalName
  The list of user principal names that we should be looking for
  .PARAMETER userId
  The lsit of user ids that we should be looking for
  .PARAMETER appDisplayName
  The name of the application that we attempted to sign into
  .PARAMETER ipAddress
  The list of ipaddresses that we should be looking for
  .PARAMETER afterDateTime
  Sign ins after this date
#>

function Get-GraphSignInAuditLogs{
  [CmdletBinding()]
  param(
    [Parameter()][string[]]$userDisplayName,
    [Parameter()][string[]]$userPrincipalName,
    [Parameter()][string[]]$userId,
    [Parameter()][string[]]$appDisplayName,
    [Parameter()][string[]]$ipAddress,
    [Parameter()][datetime]$afterDateTime
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }     
  # Create Filters for the URI
  # Create empty list
  $filters =  [System.Collections.Generic.List[PSCustomObject]]@()  
  # URI Endpoint
  $endpoint = "auditLogs/signIns"
  # Create empty list
  $signinList =  [System.Collections.Generic.List[PSCustomObject]]@()  
  # Build Filters
  # User Display Name Filter
  if($userDisplayName){
    $list =  [System.Collections.Generic.List[String]]@()
    foreach($item in $userDisplayName){
      $list.Add("userDisplayName eq '$($item)'") | Out-Null
    }
    $filters.Add("($($list -join " or "))") | Out-Null
  }  
  # User Principal Name Filter
  if($userPrincipalName){
    $list =  [System.Collections.Generic.List[String]]@()
    foreach($item in $userPrincipalName){
      $list.Add("userPrincipalName eq '$($item)'") | Out-Null
    }
    $filters.Add("($($list -join " or "))") | Out-Null
  }
  # User ID Filter
  if($userId){
    $list =  [System.Collections.Generic.List[String]]@()
    foreach($item in $userId){
      $list.Add("userId eq '$($item)'") | Out-Null
    }
    $filters.Add("($($list -join " or "))") | Out-Null
  }
  # App Display Name Filter
  if($appDisplayName){
    $list =  [System.Collections.Generic.List[String]]@()
    foreach($item in $appDisplayName){
      $list.Add("appDisplayName eq '$($item)'") | Out-Null
    }
    $filters.Add("($($list -join " or "))") | Out-Null
  }  
  # IP Address Filter
  if($ipAddress){
    $list =  [System.Collections.Generic.List[String]]@()
    foreach($item in $ipAddress){
      $list.Add("ipAddress eq '$($item)'") | Out-Null
    }
    $filters.Add("($($list -join " or "))") | Out-Null
  }    
  # Date Filter
  if($afterDateTime){
    $filters.Add("createdDateTime ge $($afterDateTime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))") | Out-Null
  }
  # Create query string for the filter
  $filterList = $filters -join " and "
  if($filterList){
    $endpoint = "$($endpoint)?`$filter=$($filterList)"
  }
  # Get Graph Headers for Call
  $headers = Get-GraphHeader  
  try{
    $uri = "https://graph.microsoft.com/beta/$($endpoint)"
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      if($results.value){
        foreach($item in $results.value){
          $signinList.Add($item) | Out-Null
        }
      }
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
    return $signinList
  }
  catch{
    throw "Unable to get devices. $($_.Exception.Message)"
  }    
}
#EndRegion '.\Public\Get-GraphSignInAuditLogs.ps1' 107
#Region '.\Public\Get-GraphUser.ps1' 0
function Get-GraphUser{
  [CmdletBinding(DefaultParameterSetName="All")]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'userPrincipalName')][ValidateNotNullOrEmpty()][string]$userPrincipalName,
    [Parameter(Mandatory = $true, ParameterSetName = 'userid')][ValidateNotNullOrEmpty()][string]$userid,
    [Parameter()][switch]$All,
    [Parameter()][switch]$ConsistencyLevel,
    [Parameter()][switch]$Count,
    [Parameter()][ValidateNotNullOrEmpty()][string]$filter,
    [Parameter()][ValidateNotNullOrEmpty()][string]$select
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  # Create empty list
  $userList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Build the headers we will use to get groups
  $ConsistencyLevelHeader = @{}
  if($ConsistencyLevel.IsPresent){
    $ConsistencyLevelHeader.Add("ConsistencyLevel",$true) | Out-Null
  }
  $headers = Get-GraphHeader @ConsistencyLevelHeader
  # Base URI for resource call
  $uri = "https://graph.microsoft.com/beta/users"   
  if($userPrincipalName){
    # Filter based on group name is required
    $uri = "$($uri)/$($userPrincipalName)"
  }
  elseif($userid){
    # Filter based on group ID
    $uri = "$($uri)/$($userid)"
  } 
  if($filter){
    $uri = "$($uri)?`$filter=$($filter)"
    if($select){
      $uri = "$($uri)&`$select=$($select)"
    }
  }
  if($count.IsPresent){
    $uri = "$($uri)&`$count=true"
  }
  try{
    # Loop until nextlink is null
    do{
      # Execute call against graph
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
      # Add results to a list variable
      foreach($item in $results.value){
        $userList.Add($item) | Out-Null
      }
      # Set the URI to the nextlink if it exists
      $uri = $results."@odata.nextLink"
    }while($null -ne $results."@odata.nextLink")
    # If there is only one result, return that
    if($userList.count -eq 0){
      return $results
    }
    else{
      # Return the group list if it exists
      return $userList
    }
  }
  catch{
    throw "Unable to get users. $($_.Exception.Message)"
  }

}

#https://graph.microsoft.com/beta/users?$filter=startsWith(jobTitle,'Occasional')
#"16e6375b-4c11-4306-be0f-b6ad3984fe89"
<#
 
    [Parameter()][switch]$signInActivity,
    [Parameter()][switch]$accountEnabled,
    [Parameter()][string]$assignedLicenses,
    [Parameter()][string]$displayName,
    [Parameter()][string]$employeeId,
    [Parameter()][string]$officeLocation,
    [Parameter()][string]$onPremisesExtensionAttributes,
    [Parameter()][string]$onPremisesSamAccountName,
    [Parameter()][string]$onPremisesUserPrincipalName,
    [Parameter()][string]$userType,
    [Parameter()][string]$department
#>

#EndRegion '.\Public\Get-GraphUser.ps1' 86
#Region '.\Public\Get-KeyVaultSecret.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is used to connect to a keyvault as the machine identity of the Azure Machine it is running under.
  .PARAMETER vaultName
  The name of the vault that we want to check
  .PARAMETER secretName
  The name of the secret we want to recover
  .EXAMPLE
  Check a secret out of a specific vault
    Get-KeyVaultSecret -vaultName <VAULT> -secretName <SECRET>
#>

function Get-KeyVaultSecret{
  [CmdletBinding()]
  [OutputType([System.String])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$vaultName,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$secretName
  )
  # The URI for the vault that we want to access
  $keyVaultURI = "https://$($vaultName).vault.azure.net/secrets/$($secretName)?api-version=2016-10-01"
  # Using the identity of the virtual machine account running the script
  $response = Invoke-RestMethod -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net' -Method GET -Headers @{Metadata="true"}
  # What the vault token is
  $keyVaultToken = $response.access_token
  try{
    # Get the relevant secret and return it
    $secret = Invoke-RestMethod -Uri $keyVaultURI -Method GET -Headers @{Authorization="Bearer $KeyVaultToken"}
    return $secret.Value | ConvertFrom-Json
  }
  # Error handling possible expected errors
  catch{
    if(($Error[0] -match "The remote name could not be resolved")){
      $message = "Error: Attempting to connect to Azure Key vault URI $($keyVaultURI)`n$($_)"
    }
    elseif(($Error[0] -match "Unauthorized")){
      $message = "Error: No authorization to Azure Key Vault URI $($keyVaultURI)`n$($_)"
    }
    elseif(($Error[0] -match "SecretNotFound")){
      $message = "Error: The secret $($secretName) is not found in Azure Key Vault URI $($keyVaultURI)`n$($_)"
    }
    else{
      $message = "Error: Unknown error connection to Azure Key vault URI $($keyVaultURI)`n$($_)"
    }
    Write-EventLog -LogName "Application" -Source "PowerShell Universal Scripts" -EntryType "Warning" -EventId 1001 -Message $message
    return $message
  }
}
#EndRegion '.\Public\Get-KeyVaultSecret.ps1' 48
#Region '.\Public\Get-MerakiNetwork.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to convert get the meraki networks
  .PARAMETER type
  The type of network that we should filter to
#>

function Get-MerakiNetwork{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateSet("wireless","systemsmanager")][string]$type
  )
  # Confirm we have a valid meraki header
  if(!$global:merakiHeader){
    throw "Please Call Connect-Meraki before calling this cmdlet"
  }
  # Generate URI for call
  $uri = "$($global:merakiApiURI)/organizations/$($global:merakiOrgId)/networks"
  # Execute API Call
  $networks = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:merakiHeader -StatusCodeVariable statusCode -FollowRelLink
  # Filter for productTypes
  $networkList = [System.Collections.Generic.List[PSCustomObject]]@()  
  foreach($network in $networks){
    foreach($item in $network){
      if($type -and $type -notin $item.productTypes){continue}
      $networkList.Add($item) | Out-Null
    }
  }
  return $networkList
}
#EndRegion '.\Public\Get-MerakiNetwork.ps1' 30
#Region '.\Public\Get-MerakiSystemsManagerDevice.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get system managed devices in a network
  .PARAMETER networkId
  The network that we want to get the devices from
  .PARAMETER fields
  Additional fields that we want to return
#>

function Get-MerakiSystemsManagerDevice{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$networkId,
    [Parameter()][ValidateNotNullOrEmpty()][string]$fields
  )
  # Confirm we have a valid meraki header
  if(!$global:merakiHeader){
    throw "Please Call Connect-Meraki before calling this cmdlet"
  }
  # Generate URI for call
  $uri = "$($global:merakiApiURI)/networks/$($networkId)/sm/devices"    
  if($fields){
    $uri = "$($uri)?fields[]=$($fields)"
  }
  # Execute API Call
  $devices = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:merakiHeader -StatusCodeVariable statusCode -FollowRelLink
  # Filter for productTypes
  $deviceList = [System.Collections.Generic.List[PSCustomObject]]@()
  foreach($device in $devices){
    foreach($item in $device){
      $deviceList.Add($item) | Out-Null
    }
  }
  return $deviceList
}
#EndRegion '.\Public\Get-MerakiSystemsManagerDevice.ps1' 35
#Region '.\Public\Get-ScriptVariables.ps1' 0
function Get-ScriptVariables{
  [CmdLetBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'JSON')][ValidateNotNullOrEmpty()][String]$JSON,
    [Parameter(Mandatory = $true,ParameterSetName = 'URI')][ValidateNotNullOrEmpty()][String]$URI,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$Environment,
    [Parameter()][ValidateNotNullOrEmpty()][String]$Script
  )
  # If path to JSON file is selected
  if($JSON){
    $vars = Get-Content -Path $JSON | ConvertFrom-JSON -Depth 10
  }
  # If a URI to a JSON is provided
  else{
    $vars = (Invoke-WebRequest -Uri $URI -Method "GET" -UseBasicParsing).Content | ConvertFrom-JSON -Depth 10
  }
  foreach ($var in $vars.PSObject.Properties) {
    if($var.Name -eq "Environment"){
      foreach($item in $var.Value.PSObject.Properties){
        if($item.Name -eq $Environment){
          foreach($obj in $item.Value.PSObject.Properties){
            Set-Variable -Name $obj.Name -Value $obj.Value -Scope Global
          }
          break
        }
      }
    }
    elseif($var.Name -eq "ScriptSpecific"){
      foreach($item in $var.Value.PSObject.Properties){
        if($item.Name -eq $Script){
          foreach($obj in $item.Value.PSObject.Properties){
            Set-Variable -Name $obj.Name -Value $obj.Value -Scope Global
          }
          break
        }
      }
    }
    else{
      Set-Variable -Name $var.Name -Value $var.Value -Scope Global
    }
  }
}
#EndRegion '.\Public\Get-ScriptVariables.ps1' 43
#Region '.\Public\Get-TopdeskAsset.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to query the assets in your enviroment
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER nameFragment
  Query a specific name fragment from the name field
  .PARAMETER searchTerm
  Add an additional search term to your filter
  .PARAMETER templateId
  Filter for specific template id for assets
  .PARAMETER templateName
  Filter for specific template name for assets
  .PARAMETER archived
  If you want to only include non-archived, or archived assets
  .PARAMETER includeArchived
  If you haven't specified archive behavior, default is false. This includes all archived as well
  .PARAMETER fields
  What fields you want to return by default
  .PARAMETER showAssignments
  If you want to show any asset assingments to locations
  .PARAMETER linkedTo
  .PARAMETER filter
  What to add a custom filter
  .RETURNS
  Returns a list of assets that fall into the parameters
  .EXAMPLE
  Return all assets
    Get-TopdeskAsset
  Return all assets for a specific asset template id
    Get-TopdeskAsset -templateid <TEMPLATEID>
  Return all assets for a specific asset template name
    Get-TopdeskAsset-templateName <TEMPLATENAME>
  Return assets with their assignments
    Get-TopdeskAsset -showAssignments $true
  Return assets starting with a specific name fragment
    Get-TopdeskAsset -nameFragment <FRAGEMENT>
#>

function Get-TopdeskAsset{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSObject]])]
  param(
    [Parameter()][string]$nameFragment,
    [Parameter()][string]$searchTerm,
    [Parameter()][string]$templateId,
    [Parameter()][string]$templateName,
    [Parameter()][bool]$archived = $false,
    [Parameter()][bool]$includeArchived = $false,
    [Parameter()][string]$fields,
    [Parameter()][switch]$showAssignments,
    [Parameter()][string]$linkedTo,
    [Parameter()][string]$filter
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }    
  # Base URI for retrieval
  $baseuri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/assetmgmt/assets?`$orderby=name asc&fields=text,archived,id,name,$($fields)"
  # Filter base on either template ID or template Name
  if($templateId){
    $baseuri = "$($baseuri)&templateId=$($templateId)"
  }
  elseif($templateName){
    $baseuri = "$($baseuri)&templateName=$($templateName)"
  }
  # Fragment of the name
  if($nameFragment){
    $baseuri = "$($baseuri)&nameFragment=$($nameFragment)"
  }
  # Search term
  if($searchTerm){
    $baseuri = "$($baseuri)&searchTerm=$($searchTerm)"
  }
  # If the devices are archived or not
  if($archived){
    $baseuri = "$($baseuri)&archived='true'"
  }
  elseif($archived -eq $false -and -not $includeArchived){
    $baseuri = "$($baseuri)&archived='false'"
  }
  # If we want to show the assignments for the asset
  if($showAssignments){
    $baseuri = "$($baseuri)&showAssignments=$($showAssignments)"
  }    
  # Custom filter
  if($filter){
    $baseuri = "$($baseuri)&`$filter=$($filter)"
  }  
  # If it is linked to specific id
  if($linkedTo){
    $baseuri = "$($baseuri)&linkedTo=$($linkedTo)"
  }   
  # Set the first URI to query
  $uri = $baseuri
  try{
    # Empty List for Results
    $data = [System.Collections.Generic.List[PSObject]]@()    
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
      foreach($item in $results.dataSet){
        $data.Add($item) | Out-Null
      }
      # Set next URI if we are going to need to loop again.
      $uri = "$($baseuri)&lastSeenName='$($data[$data.count - 1].Name)'&lastSeenOrderbyValue='$($data[$data.count - 1].Name)'"
      Write-Verbose "Next Query: $($uri)"
      Write-Verbose "Found $($data.count) assets."
    } while($statusCode -eq 206)
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # Return results
  return $data

}
#EndRegion '.\Public\Get-TopdeskAsset.ps1' 119
#Region '.\Public\Get-TopdeskAssetDropdown.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get the values of a asset dropdown field
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER dropdownId
  The id of the dropdown field that trying to query
  .PARAMETER dropdownName
  The name of the dropdown field trying to query
  .PARAMETER includeArchived
  Should we include archived items
  .RETURNS
  Returns a list of dropdown values that fall into the parameters
  .EXAMPLE
  Return values for a specific dropdown by name
    Get-TopdeskAssetDropdown -dropdownName <DROPDOWNNAME>
  Return values for a specific dropdown by id
    Get-TopdeskAssetDropdown dropdownId <DROPDOWNNAME>
#>

function Get-TopdeskAssetDropdown{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'dropdownId')][string]$dropdownId,
    [Parameter(Mandatory = $true,ParameterSetName = 'dropdownName')][string]$dropdownName,
    [Parameter()][bool]$includeArchived = $false
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }    
  # We require either the ID or the Name of the dropdown that we are looking for
  if($dropdownId){
    $dropdown = $dropdownId
  }
  elseif($dropdownName){
    $dropdown = $dropdownName
  }
  # Base URI
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/assetmgmt/dropdowns/$($dropdown)?field=name"
  # Include archived if requested
  if($includeArchived){
    $uri = "$($uri)&includeArchived=$($includeArchived)"
  }  
  try{
    # Get data from the API
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # Return results
  return $results.results
}
#EndRegion '.\Public\Get-TopdeskAssetDropdown.ps1' 56
#Region '.\Public\Get-TopdeskAssetTemplates.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return a list of asset templates that are in your topdesk enviroment
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER searchTerm
  If you are looking for a specific template you can search for it.
  .RETURNS
  Returns a list of asset templates
  .EXAMPLE
  Return all asset templates
    Get-TopdeskAssetTemplates
  Return only asset templates that start with the word Apple
    Get-TopdeskAssetTemplates -searchTerm "Apple"
#>

function Get-TopdeskAssetTemplates{
  [CmdletBinding()]
  param(
    [Parameter()][string]$searchTerm    
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }      
  # Base URI for the list of asset management templates
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/assetmgmt/templates"
  # If search term is passed, add that to URI to filter
  if($searchTerm){
    $uri = "$($uri)?searchTerm=$($searchTerm)"
  }
  try{
    # Retrieve data from API
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # Return the data retrieved
  return $results.dataSet
}
#EndRegion '.\Public\Get-TopdeskAssetTemplates.ps1' 43
#Region '.\Public\Get-TopdeskBranches.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return the branches in your enviroment
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER fields
  The fields that return from the data. Does not work when using the ID filter
  .PARAMETER id
  The id of the subcategory you are trying to retrieve
  .PARAMETER startsWith
  The start of the name of the branches
  .PARAMETER clientReferenceNumber
  The start of the client reference number
  .RETURNS
  Returns a list of branches
  .EXAMPLE
  Return all branches
    Get-TopdeskBranches
  Return specific branch by ID
    Get-TopdeskBranches -id <ID>
  Return specific branch where the name starts with
    Get-TopdeskBranches -startsWith <NAME>
  Return specific fields
    Get-TopdeskBranches -fields <FIELDS>
#>

function Get-TopdeskBranches{
  [CmdletBinding()]
  param(
    [Parameter()][string]$fields,
    [Parameter()][string]$id,
    [Parameter()][string]$startsWith,
    [Parameter()][string]$clientReferenceNumber
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }     
  # The base URI for the branches endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/branches?"
  # If ID, get details of specific branch. This is more detailed, and does not allow additional filtering
  if($id){
    $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/branches/id/$($id)?"
  }
  else{
    # By default the query is null
    $query = $null    
    # If only want specific fields, set what fields
    if($fields){
      $uri = "$($uri)`$fields=$($fields)"
    }  
    # If Name is populated, filter based on the name, using starts with
    if($startsWith){
      $query = "&query=name=sw=$($startsWith)"
    }
    # If Client Reference Number is populated, filter using starts with
    if($clientReferenceNumber){
      if($null -eq $query){$query = "&query="}
      else{$query = "$($query);"}
      $query = "$($query)clientReferenceNumber=sw=$($clientReferenceNumber)"
    }    
    # If query is not blank, add it to the URI
    if($null -ne $query){
      $uri = "$($uri)$($query)"
    }    
  }
  try{
    # Get base data from Topdesk
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # Return results
  $results
}
#EndRegion '.\Public\Get-TopdeskBranches.ps1' 78
#Region '.\Public\Get-TopdeskChangeProgress.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return the change progress text for specific change
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER changeID
  The id of the change that you want the original request text from.
  .EXAMPLE
  Return change request progress
    Get-TopdeskChangeProgress -changeID <CHANGEID>
#>

function Get-TopdeskChangeProgress{
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$changeID  
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }      
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/operatorChanges/$($changeID)/progresstrail"
  try{
    # Invoke API to get the details
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  } 
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }  
  # Return the details of the ticket
  return $results.results
}
#EndRegion '.\Public\Get-TopdeskChangeProgress.ps1' 34
#Region '.\Public\Get-TopDeskChangeRequest.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to query operator changes in Topdesk and return their details
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER query
  If you want to add a filtered query to the details
  .PARAMETER sort
  If there is a parameter you want to sort by
  .PARAMETER direction
  The direction of the sort only applicable if sort is set
  .PARAMETER pageSize
  The number of results you want to return
  .PARAMETER pageStart
  If you want to skip a certain number of results and start your retrieval there
  .PARAMETER fields
  What fields you want to retrieve from the API
  .PARAMETER all
  If you want to retrieve all relevant results, or just the first page
  .RETURNS
  Returns a list of changes that fall into the parameters
  .EXAMPLE
  Return the first page based on default sort
    Get-TopDeskChangeRequest
  Return all pages based on default sort
    Get-TopDeskChangeRequest -all
  Return a specific change based on the query function
    Get-TopDeskChangeRequest -query "number=='<CHANGENUMBER>'"
#>

function Get-TopDeskChangeRequest{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSObject]])]
  param(
    [Parameter()][string]$query,
    [Parameter()][ValidateSet("id","creationDate","simple.closedDate","simple.plannedImplementationDate","simple.plannedStartDate","phases.rfc.plannedEndDate","phases.progress.plannedEndDate","phases.evaluation.plannedEndDate")]
      [string]$sort,
    [Parameter()][ValidateSet("asc","desc")][string]$direction = "asc",
    [Parameter()][string]$pageSize,
    [Parameter()][int]$pageStart = 0,
    [Parameter()][string]$fields,
    [Parameter()][switch]$all
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }       
  try{
    # Base URI for Operator Changes.
    $base = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/operatorChanges?pageStart=$($pageStart)"
    # If we are expecting a different page size from default
    if($pageSize -ne ""){
      $base = "$($base)&pageSize=$($pageSize)"
    }
    # If we want to sort based on specific criteria, and the direction of that sort
    if($sort -ne ""){
      $base = "$($base)&sort=$($sort):$($direction)"
    }   
    # If we have added a query to our API call
    if($query -ne ""){
      $base = "$($base)&query=$($query)"
    }         
    # What fields to return if we want something other than the default
    if($fields -ne ""){
      $base = "$($base)&fields=$($fields)"
    }     
    # Set the initial URI to what we determined for the URI
    $uri = $base
    # Put inside a loop in case we want to get all. Only loop if result code is 206 and set to all
    $data = [System.Collections.Generic.List[PSObject]]@()
    do{
      # Query API based on URI
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
      # Populate the URI to the next link returned by API
      $uri = $results.next
      # Loop through results and add to list
      foreach($item in $results.results){
        $data.Add($item) | Out-Null
      }
      Write-Verbose "Next Query: $($uri)"
      Write-Verbose "Found $($data.count) tickets."      
    }while($statusCode -eq 206 -and $all.IsPresent -ne $false)
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  return $data
}
#EndRegion '.\Public\Get-TopDeskChangeRequest.ps1' 90
#Region '.\Public\Get-TopdeskChangeRequestText.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return the change request text for specific change
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER changeID
  The id of the change that you want the original request text from.
  .EXAMPLE
  Return change request text
    Get-TopdeskChangeRequestText -changeID <CHANGEID>
#>

function Get-TopdeskChangeRequestText{
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$changeID  
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }   
  # Base URI for change request data
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/operatorChanges/$($changeID)/requests"
  try{
    # Invoke API to get the details
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  } 
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # Return the details of the ticket
  return $results.results
}
#EndRegion '.\Public\Get-TopdeskChangeRequestText.ps1' 35
#Region '.\Public\Get-TopdeskIncidentCategory.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return the categories you have configured for your incidents
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER id
  The id of the category you are trying to retrieve
  .PARAMETER name
  The name of the category you are trying to retrieve
  .RETURNS
  Returns a list of categories
  .EXAMPLE
  Return all categories
    Get-TopdeskIncidentCategory
  Return specific category by ID
    Get-TopdeskIncidentCategory -id <ID>
  Return specific category by Name
    Get-TopdeskIncidentCategory -name <NAME>
#>

function Get-TopdeskIncidentCategory{
  [CmdletBinding()]
  param(
    [Parameter()][string]$id,
    [Parameter()][string]$name
  ) 
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }    
  # Topdesk URI for incident categories
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/incidents/categories"
  try{
    # Retrieve data from API
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # Filter for either Id or Name, or return all if neither specificed
  if($id){
    return $results | Where-Object {$_.id -eq $id}
  }
  elseif($name){
    return $results | Where-Object {$_.name -eq $name}
  }
  else{
    return $results
  }
}
#EndRegion '.\Public\Get-TopdeskIncidentCategory.ps1' 52
#Region '.\Public\Get-TopdeskIncidentSearchList.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will recover the details of the optional fields search lists. It will
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER tab
  What tabs to get data from. This is an array
  .PARAMETER searchList
  What search lists to check for data. This is an array
  .PARAMETER includeEmpty
  Return empty data
  .RETURNS
    This returns a hashtable with the tabs and values.
  .EXAMPLE
  Return all search lists data
    Get-TopdeskIncidentSearchList
  Return all search lists data that are not empty
    Get-TopdeskIncidentSearchList -includeEmpty $false
#>

function Get-TopdeskIncidentSearchList{
  [CmdletBinding()]
  [OutputType([hashtable])]
  param(
    [Parameter()][int[]]$tab = @(1,2),
    [Parameter()][int[]]$searchList = @(1,2,3,4,5),
    [Parameter()][bool]$includeEmpty = $true
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }     
  # Iniatilize a empty list.
  $list = @{}
  # Loop through the tabs that hold the search lists
  foreach($t in $tab){
    # Iniatilize a empty sublist
    $sublist = @{}
    # Loop through the items in the tab
    foreach($l in $searchList){
      # Set the URI for the search list to get details of
      $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/incidents/free_fields/$($t)/searchlists/$($l)"
      try{
        # Query API for data
        $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
        # If Include Empty is false then skip anything that is blank
        if($null -eq $results.id -and $includeEmpty -eq $false){continue}
        # Add Results to Sublist
        $sublist.Add($l,$results) | Out-Null        
      }
      catch{
        throw "Error Accessing API at $($uri). $($Error[0])"
      }                  
    }
    # If include empty is false, and the sublist is empty, skip.
    if($sublist.Count -eq 0 -and $includeEmpty -eq $false){continue}
    # Add sublist to the over all list
    $list.Add($t,$sublist) | Out-Null    
  }  
  # Return data
  return $list  
}
#EndRegion '.\Public\Get-TopdeskIncidentSearchList.ps1' 64
#Region '.\Public\Get-TopdeskIncidentSubcategory.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return the subcategories that you have in your incident module
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER id
  The id of the subcategory you are trying to retrieve
  .PARAMETER name
  The name of the subcategory you are trying to retrieve
  .PARAMETER category_id
  The id of the category you are trying to retrieve
  .PARAMETER category_name
  The name of the category you are trying to retrieve
  .RETURNS
  Returns a list of subcategories
  .EXAMPLE
  Return all subcategories
    Get-TopdeskIncidentSubcategory
  Return specific subcategory by ID
    Get-TopdeskIncidentSubcategory -id <ID>
  Return specific subcategory by Name
    Get-TopdeskIncidentSubcategory <NAME>
  Return specific subcategory by what category id it is in
    Get-TopdeskIncidentSubcategory -category_id <CATEGORYID>
  Return specific subcategory by what category name it is in
    Get-TopdeskIncidentSubcategory -category_name <NAME>
#>

function Get-TopdeskIncidentSubcategory{
  [CmdletBinding()]
  param(
    [Parameter()][string]$id,
    [Parameter()][string]$name,
    [Parameter()][string]$category_id,
    [Parameter()][string]$category_name
  ) 
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }   
  # Base URI for subcategory data
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/incidents/subcategories"
  try{
    # Get base data from Topdesk
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
  # If looking for specific id, just return it
  if($id){
    return $results | Where-Object {$_.id -eq $id}
  }
  else{
    # If filtering by name, narrow results to match name
    if($name){
      $results = $results | Where-Object {$_.name -eq $name}
    }
    # If filtering by category id, narrow results to match
    if($category_id){
      $results = $results | Where-Object {$_.category.id -eq $category_id}
    }
    # If filtering by category name, narrow results to match
    if($category_name){
      $results = $results | Where-Object {$_.category.name -eq $category_name}
    }
    # Return data
    return $results
  }  
}
#EndRegion '.\Public\Get-TopdeskIncidentSubcategory.ps1' 72
#Region '.\Public\Get-TopdeskOperator.ps1' 0
function Get-TopdeskOperator{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$query,
    [Parameter()][ValidateNotNullOrEmpty()][string]$fields
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }   
  # Default Page Size. Between 1 and 100
  $pageSize = 10
  # The base URI for the branches endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/operators?start=0&page_size=$($pageSize)"  
  # If query set add it to the uri
  if($query){
    $uri  = "$($uri)&query=$($query)"
  }
  # If fields set filter to those fields
  if($fields){
    $uri  = "$($uri)&fields=$($fields)"
  }
  try{
    $data = [System.Collections.Generic.List[PSObject]]@()
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
      foreach($item in $results){
        $data.Add($item) | Out-Null
      }
      $currentStart = [Regex]::Match($uri,"\d+")
      $uri = $uri -replace "start=$($currentStart.Value)","start=$($currentStart.Value/1 + $pageSize)"
    } while($statusCode -eq 206)
    return $data
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
}
#EndRegion '.\Public\Get-TopdeskOperator.ps1' 39
#Region '.\Public\Get-TopdeskOperatorGroup.ps1' 0
function Get-TopdeskOperatorGroup{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$query,
    [Parameter()][ValidateNotNullOrEmpty()][string]$fields
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }   
  # Default Page Size. Between 1 and 100
  $pageSize = 10
  # The base URI for the branches endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/operatorgroups?start=0&page_size=$($pageSize)"  
  # If query set add it to the uri
  if($query){
    $uri  = "$($uri)&query=$($query)"
  }
  # If fields set filter to those fields
  if($fields){
    $uri  = "$($uri)&fields=$($fields)"
  }
  try{
    $data = [System.Collections.Generic.List[PSObject]]@()
    do{
      $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
      foreach($item in $results){
        $data.Add($item) | Out-Null
      }
      $currentStart = [Regex]::Match($uri,"\d+")
      $uri = $uri -replace "start=$($currentStart.Value)","start=$($currentStart.Value/1 + $pageSize)"
    } while($statusCode -eq 206)
    return $data
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  }
}
#EndRegion '.\Public\Get-TopdeskOperatorGroup.ps1' 39
#Region '.\Public\Get-TopdeskOrderedItems.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will return any ordered items that are attached to the specific change id
  .PARAMETER credential
  The credential to connect to the API endpoints
  .PARAMETER environment
  The topdesk enviroment URI. For examble https://test.topdesk.net then this value would be test
  .PARAMETER changeID
  The id of the change that you want to look for items on
  .RETURNS
  Returns a list of ordered items
  .EXAMPLE
  Return the ordered items of a change
    Get-TopdeskOrderedItems-changeID <CHANGEID>
#>

function Get-TopdeskOrderedItems{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$changeID
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }     
  # URI to get ordered items of a change
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/operatorChanges/$($changeID)/orderedItems"
  try{
    # Query the API for the relevant data
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
  } 
  catch{
    throw $Error[0]
  } 
  # Return results
  return $results.results
}
#EndRegion '.\Public\Get-TopdeskOrderedItems.ps1' 37
#Region '.\Public\Get-Values.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to help PSU scripts for display of values
  .PARAMETER fields
  What fields to display
#>

function Get-Values(){
  param(
    [Parameter(Mandatory)][string[]]$fields,
    [Parameter(Mandatory)][PSCustomObject]$item
  )  
  $value = ""
  # Loop through the array to take not of what values were selected, and then return that value.
  foreach($i in $fields){
    if($item.$i){
      if($i.Contains('-1')){$value += $i.Substring(0,$i.Length-2) + ","}
      else{$value += $i + ","}
    }
  }
  return $value.Replace("-"," ")
}
#EndRegion '.\Public\Get-Values.ps1' 22
#Region '.\Public\Get-YesNo.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to help PSU scripts for display of yes/no
  .PARAMETER fields
  What fields to display
#>

function Get-YesNo(){
  [CmdletBinding()]
  [OutputType([string])]
  param(
    [Parameter(Mandatory = $true)][string[]]$fields,
    [Parameter(Mandatory)][PSCustomObject]$item
  )
  # Loop through values that are passed, and if exist, return yes, otherwise return No
  foreach($i in $fields){
    if($item.$i){return "Yes"}
  }
  return "No"
}  
#EndRegion '.\Public\Get-YesNo.ps1' 20
#Region '.\Public\Move-GraphMail.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to move emails between folders in a mailbox
  .PARAMETER id
  The id of the mail message we are acting on
  .PARAMETER emailAddress
  The email address of the account that we are reading from
  .PARAMETER folder
  The id of the folder that we are moving the message to
#>

function Move-GraphMail{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$id,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$emailAddress,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$folder
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  $headers = Get-GraphHeader
  # Body Content
  $body = @{
    "destinationId" = $folder
  } | ConvertTo-Json 
  $uri = "https://graph.microsoft.com/beta/users/$($emailAddress)/messages/$($id)/move"
  $results = Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -Body $body -StatusCodeVariable statusCode
  # Return Results
  if($statusCode -in (200,201)){
    return $results
  }
  else{
    throw "Unable to move email."
  }      
}
#EndRegion '.\Public\Move-GraphMail.ps1' 36
#Region '.\Public\Move-MerakiSystemsManagerDevice.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to move devices from one network to another
  .PARAMETER networkId
  The network that we want to get the devices from
  .PARAMETER newNetworkId
  The network that we want to get the devices to
  .PARAMETER ids
  If we want to move devices based on their id
  .PARAMETER serialNumbers
  If we want to move devices based on their serial number
#>

function Move-MerakiSystemsManagerDevice{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$networkId,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$newNetworkId,
    [Parameter()][ValidateNotNullOrEmpty()][System.Object]$ids,
    [Parameter()][ValidateNotNullOrEmpty()][System.Object]$serialNumbers
  )
  # Confirm we have a valid meraki header
  if(!$global:merakiHeader){
    throw "Please Call Connect-Meraki before calling this cmdlet"
  }
  # Confirm we have items to move
  if(-not $ids -and -not $serialNumbers){
    throw "You need to specify at least one id or one serial number"
  }
  # Generate URI for call
  $uri = "$($global:merakiApiURI)/networks/$($networkId)/sm/devices/move"   
  # Create Body
  $body = @{
    "newNetwork" = $newNetworkId
  }
  if($ids){
    $body.Add("ids",$ids)
  }
  if($serialNumbers){
    $body.Add("serials",$serialNumbers)
  }
  # Try to move devices
  try{
    $results = Invoke-RestMethod -Method "Post" -Uri $uri -Headers $global:merakiHeader -Body ($body | ConvertTo-Json) -StatusCodeVariable statusCode
  }
  catch{
    throw "Unable to move devices. $($_.Exception.Message)"
  }
}
#EndRegion '.\Public\Move-MerakiSystemsManagerDevice.ps1' 49
#Region '.\Public\New-AlertFactoryIncident.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create a new Topdesk Support Ticket
  .PARAMETER details
  The details that we will be using for the ticket
  .PARAMETER emails
  The emails that will be part of this incident
  .PARAMETER sourceDir
  The directory that the process is running from, so it can save the emails to html files
#>

function New-AlertFactoryIncident{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][hashtable]$details,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$emails,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$sourceDir,
    [Parameter()][ValidateNotNullOrEmpty()][int]$count = 1
  )
  $body = @{}
  foreach($item in $details.GetEnumerator()){
    if($null -ne $item.Value){
      switch($item.key){
        "email"{
          $body.Add("caller_dynamicName",$item.Value)
          $body.Add("caller_email",$item.Value)
        }
        "location" {
          $body.Add("caller_branch_id",(Get-TopdeskBranches -clientReferenceNumber $item.Value | Select-Object -first 1).id)
        }
        "briefDescription" {
          $description = $item.Value
          if($count -gt 1){
            $description = "$($description) (Has Occured $($count) times)"
          }
          $body.Add("briefDescription",$description)
        }
        "callType" {
          $body.Add("callType_name",$item.Value)
          
        }
        "category" {
          $body.Add("category_name",$item.Value)
        }
        "subcategory" {
          $body.Add("subcategory_name",$item.Value)
        }
        "impact" {
          $body.Add("impact_name",$item.Value)
        }
        "urgency" {
          $body.Add("urgency_name",$item.Value)
        }
        "priorty" {
          $body.Add("priority_name",$item.Value)
        }
        "duration" {
          $body.Add("duration_name",$item.Value)
        }
        "operatorGroup" {
          $body.Add("operatorGroup_id",(Get-TopdeskOperatorGroup -query "groupName=='$($item.value)'" -fields id | Select-Object -first 1).id)
        }
        "operator" {
          $body.Add("operator_id",(Get-TopdeskOperator -query "dynamicName=='$($item.value)'" -fields id | Select-Object -first 1).id)
        }
        "status" {
          $body.Add("processingStatus_name",$item.Value)
        }
        "request" {
          $body.Add("request",((ConvertFrom-HTML -Content ($item.value -replace "</p>","\r\n")).innerText -replace "\\r\\n","<br/>"))
        }
      }
    }
  }
  try{
    $incident = New-TopdeskIncident @body
    foreach($email in $emails)
    {
      $tempFile = Join-Path -Path $sourceDir -ChildPath "Temp" -AdditionalChildPath "$(Get-Random).html"
      $email.body.content | Out-File $tempFile
      Add-TopdeskIncidentAttachment -id $incident.id -filepath $tempFile
    }
    return $incident
  }
  catch{
    throw "Unable to create incident. $($Error[0])"
  }
}
#EndRegion '.\Public\New-AlertFactoryIncident.ps1' 88
#Region '.\Public\New-GraphGroup.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create a new Entra ID group via graph
  .PARAMETER displayName
  The display name of the group
  .PARAMETER mailEnabled
  If the group should be mail enabled, default false
  .PARAMETER mailNickname
  What the mailnickname will be, required even if mailenabled is false
  .PARAMETER description
  The description for the group
  .PARAMETER securityEnabled
  If the group should be security enabled, default true
#>

function New-GraphGroup{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][string]$displayName,
    [Parameter()][bool]$mailEnabled = $false,
    [Parameter(Mandatory = $true)][string]$mailNickname,
    [Parameter()][string]$description,
    [Parameter()][bool]$securityEnabled = $true
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  # Build the headers we will use to get groups
  $headers = Get-GraphHeader  
  # Variables
  $body = $PsBoundParameters | ConvertTo-Json  
  # Base URI for resource call
  $uri = "https://graph.microsoft.com/beta/groups"
  try{
    # Execute call against graph
    $results = Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -Body $body -StatusCodeVariable statusCode    
    return $results
  }
  catch{
    throw "Unable to create group. $($_.Exception.Message)"
  }
  
}
#EndRegion '.\Public\New-GraphGroup.ps1' 44
#Region '.\Public\New-RandomString.ps1' 0
<#
  .DESCRIPTION
  This cmdet will generate a random character string based on inputs passed to it.
  .PARAMETER length
  The number of characters you want the random string to contain.
  .PARAMETER characters
  The list of characters that you want it to use to generate the random string
  .EXAMPLE
  New-RandomString -length 10 -characters 'abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890!@#$%^&*'
    Will Generate a random string of 10 characters in length with the characters in abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890!@#$%^&*
#>

function New-RandomString{
  [CmdletBinding()]
  [OutputType([System.String])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][int]$length,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$characters
  )
  # Generate a random string based on the length and characters passed
  $randomString = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length}
  $private:ofs = ""
  return [string]$characters[$randomString]
}
#EndRegion '.\Public\New-RandomString.ps1' 24
#Region '.\Public\New-TopdeskAssetUpload.ps1' 0
function New-TopdeskAssetUpload{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$filePath,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$fileName,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$TopdeskTenant
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }   
  $global:topdeskAccessToken."Content-Type" = "application/octet-stream"
  try{
    $uri = "https://$($TopdeskTenant).topdesk.net/services/import-to-api-v1/api/sourceFiles?filename=$($fileName)"
    Invoke-RestMethod -Uri $uri -Method PUT -headers $global:topdeskAccessToken -InFile $filePath
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[1])"
  }
}
#EndRegion '.\Public\New-TopdeskAssetUpload.ps1' 21
#Region '.\Public\New-TopdeskIncident.ps1' 0
function New-TopdeskIncident{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'registeredCaller')][ValidateNotNullOrEmpty()][string]$caller_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_branch_id,
    [Parameter(Mandatory = $true,ParameterSetName = 'nonregisteredCaller')][ValidateNotNullOrEmpty()][string]$caller_dynamicName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_phoneNumber,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_mobileNumber,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_email,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_department_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_department_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_location_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_budgetHolder_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_budgetHolder_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_personExtraFieldA_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_personExtraFieldA_name,   
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_personExtraFieldB_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_personExtraFieldB_name,     
    [Parameter()][ValidateNotNullOrEmpty()][string]$caller_callerLookup_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$status,
    [Parameter()][ValidateNotNullOrEmpty()][string]$briefDescription,
    [Parameter()][ValidateNotNullOrEmpty()][string]$request,
    [Parameter()][ValidateNotNullOrEmpty()][string]$action,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$actionInvisibleForCaller,
    [Parameter()][ValidateNotNullOrEmpty()][string]$entryType_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$entryType_name,    
    [Parameter()][ValidateNotNullOrEmpty()][string]$callType_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$callType_name, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$category_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$category_name,    
    [Parameter()][ValidateNotNullOrEmpty()][string]$subcategory_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$subcategory_name, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$externalNumber,       
    [Parameter()][ValidateNotNullOrEmpty()][string]$object_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$object_name, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$location_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$branch_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$mainIncident_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$mainIncident_number, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$impact_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$impact_name, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$urgency_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$urgency_name, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$priority_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$priority_name, 
    [Parameter()][ValidateNotNullOrEmpty()][string]$duration_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$duration_name,    
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$targetDate,
    [Parameter()][ValidateNotNullOrEmpty()][string]$sla_id,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$onHold,
    [Parameter()][ValidateNotNullOrEmpty()][string]$operator_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$operatorGroup_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$supplier_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$processingStatus_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$processingStatus_name,  
    [Parameter()][ValidateNotNullOrEmpty()][bool]$responded,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$responseDate,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$completed,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$completedDate,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$closed,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$closedDate,
    [Parameter()][ValidateNotNullOrEmpty()][string]$closureCode_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$closureCode_name, 
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$costs,
    [Parameter()][ValidateNotNullOrEmpty()][int]$feedbackRating,
    [Parameter()][ValidateNotNullOrEmpty()][string]$feedbackMessage, 
    [Parameter()][ValidateNotNullOrEmpty()][bool]$majorCall,
    [Parameter()][ValidateNotNullOrEmpty()][string]$majorCallObject_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$majorCallObject_number, 
    [Parameter()][ValidateNotNullOrEmpty()][bool]$publishToSsd,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields1_boolean1,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields1_boolean2,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields1_boolean3,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields1_boolean4,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields1_boolean5,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields1_number1,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields1_number2,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields1_number3,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields1_number4,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields1_number5,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_text1,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_text2,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_text3,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_text4,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_text5,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_memo1,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_memo2,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_memo3,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_memo4,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_memo5,    
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields1_date1,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields1_date2,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields1_date3,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields1_date4,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields1_date5,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist1_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist1_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist2_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist2_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist3_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist3_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist4_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist4_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist5_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields1_searchlist5_name,                
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields2_boolean1,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields2_boolean2,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields2_boolean3,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields2_boolean4,
    [Parameter()][ValidateNotNullOrEmpty()][bool]$optionalFields2_boolean5,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields2_number1,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields2_number2,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields2_number3,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields2_number4,
    [Parameter()][ValidateNotNullOrEmpty()][decimal]$optionalFields2_number5,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_text1,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_text2,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_text3,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_text4,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_text5,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_memo1,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_memo2,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_memo3,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_memo4,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_memo5,    
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields2_date1,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields2_date2,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields2_date3,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields2_date4,
    [Parameter()][ValidateNotNullOrEmpty()][datetime]$optionalFields2_date5,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist1_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist1_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist2_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist2_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist3_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist3_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist4_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist4_name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist5_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$optionalFields2_searchlist5_name,      
    [Parameter()][ValidateNotNullOrEmpty()][string]$externalLink_id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$externalLink_type,
    [Parameter()][ValidateNotNullOrEmpty()][string]$externalLink_date
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }    
  $body = @{}
  foreach($item in $PsBoundParameters.GetEnumerator()){
    $key = $item.Key.split("_")
    if($key.count -gt 1){
      $parent = ""
      for($i = 0; $i -lt $key.count - 1; $i++){
        if($i -eq 0){
          if(!$body.ContainsKey($key[$i])){
            $body.Add($key[$i],@{}) | Out-Null
          }
          $parent = $key[$i]
        }
        else{
          $scriptBlock = "
            if(!`$body.$($parent).ContainsKey(`"$($key[$i])`")){
              `$body.$($parent).Add(`"$($key[$i])`",@{}) | Out-Null
            }
            `$parent = `"$($parent).$($key[$i])`"
          "

          Invoke-Expression $scriptBlock
        }
      }
      $scriptBlock = "
        `$body.$($parent).Add(`"$($key[$i])`",`$item.value)
      "

      Invoke-Expression $scriptBlock
    }
    else{
      if(!$body.ContainsKey($item.Key)){
        $body.Add($item.Key,$item.Value) | Out-Null
      }
    }
  }
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/incidents"
  try{
    $results = Invoke-RestMethod -Method POST -Uri $uri -Headers $global:topdeskAccessToken -body ($body | ConvertTo-Json) -StatusCodeVariable statusCode
  }
  catch{
    throw "Error Accessing API at $($uri). $($Error[0])"
  } 
  return $results  
}
#EndRegion '.\Public\New-TopdeskIncident.ps1' 191
#Region '.\Public\Remove-GraphGroupMember.ps1' 0
function Remove-GraphGroupMember{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$groupId,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string[]]$ids
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # Get Graph Headers for Call
  $headers = Get-GraphHeader 
  try{
    $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
    $batches = [System.Collections.Generic.List[PSCustomObject]]@()
    for($i = 1; $i -le $ids.Count; ++$i){
      $obj = [PSCustomObject]@{
        "id" = $i
        "method" = "DELETE"
        "url" = "/groups/$($groupId)/members/$($ids[$i-1])/`$ref"
      }      
      $batchObj.Add($obj) | Out-Null        
      if($($i % 20) -eq 0){
        $batches.Add($batchObj) | Out-Null
        $batchObj = $null
        $batchObj = [System.Collections.Generic.List[PSCustomObject]]@()
      }      
    }
    $batches.Add($batchObj) | Out-Null
    foreach($batch in $batches){
      $json = [PSCustomObject]@{
        "requests" = $batch
      } | ConvertTo-JSON -Depth 5
      $results = Invoke-RestMethod -Method "POST" -Uri "https://graph.microsoft.com/beta/`$batch" -Headers $headers -Body $json
    }
  }
  catch{
    throw "Unable to remove members. $($_.Exception.Message)"
  }      
}
#EndRegion '.\Public\Remove-GraphGroupMember.ps1' 41
#Region '.\Public\Remove-GraphIntuneApp.ps1' 0
function Remove-GraphIntuneApp{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$applicationid
  )
  # Invoke graph API to remove the application
  $endpoint = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($applicationid)"
  $headers = Get-GraphHeader
  Invoke-RestMethod -Method Delete -Uri $endpoint -Headers $headers -StatusCodeVariable statusCode | Out-Null
}
#EndRegion '.\Public\Remove-GraphIntuneApp.ps1' 11
#Region '.\Public\Remove-TopdeskAssetAssignment.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will remove a location assignment for an asset
  .PARAMETER asset
  The asset that we want to update with the assignments listed
  .PARAMETER locationID
  The location ID that we want to remove
  .EXAMPLE
#>

function Remove-TopdeskAssetAssignment{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][PSCustomObject]$asset,
    [Parameter(Mandatory = $true)][string]$locationID   
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }    
  if($null -eq $asset.'@assignments'.locations){
    throw "Asset has no location assignments"
  }
  foreach($link in $asset.'@assignments'.locations){
    if($link.branch.id -eq $locationID){
      $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/assetmgmt/assets/$($asset.unid)/assignments/$($link.linkid)"
      Invoke-RestMethod -Method DELETE -Uri $uri -Headers $global:topdeskAccessToken -StatusCodeVariable statusCode
    }
  }
}
#EndRegion '.\Public\Remove-TopdeskAssetAssignment.ps1' 30
#Region '.\Public\Set-GraphMailRead.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to mark a specific email as read
  .PARAMETER id
  The id of the mail message we are acting on
  .PARAMETER emailAddress
  The email address of the account that we are reading from
#>

function Set-GraphMailRead{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$id,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$emailAddress
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  $headers = Get-GraphHeader
  # Body Content
  $body = @{
    "isRead" = $true
  } | ConvertTo-Json
  # Execute Graph Call
  $uri = "https://graph.microsoft.com/beta/users/$($emailAddress)/messages/$($id)"
  $results = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body -StatusCodeVariable statusCode
  # Return Results
  if($statusCode -in (200,201)){
    return $results
  }
  else{
    throw "Unable to mark email as read."
  }  
}
#EndRegion '.\Public\Set-GraphMailRead.ps1' 34
#Region '.\Public\Test-AlertFactoryRule.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to test rules against emails that are in the mailbox for alert factory
  .PARAMETER rule
  The rule we are testing against
  .PARAMETER emails
  The emails that will be part of this incident
#>

function Test-AlertFactoryRule{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$rule,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$emails
  )
  foreach($obj in $rule.PSObject.Properties){
    if($null -ne $obj.value.type){
      switch($obj.name){
        "sender" {
          $lookupField = "from.emailAddress.Address"
        }
        "body" {
          $lookupField = "body.content"
        }
        "subject" {
          $lookupField = "subject"
        }
      }        
      switch($obj.value.type){
        "equals" {
          $command = "[System.Collections.Generic.List[PSObject]]`$emails = `$emails | Where-Object {`$_.$($lookupField) -eq `"$($obj.value.expression)`"}"
          
        }
        "contains" {
          $command = "[System.Collections.Generic.List[PSObject]]`$emails = `$emails | Where-Object {`$_.$($lookupField) -match `"$($obj.value.expression)`"}"
        }
        "regex" {
          $command = "[System.Collections.Generic.List[PSObject]]`$emails = `$emails | Where-Object {`$_.$($lookupField) -match `"$($obj.value.expression)`"}"
        }        
      }
      Invoke-Expression -Command $command
    }
  }
  return $emails
}
#EndRegion '.\Public\Test-AlertFactoryRule.ps1' 45
#Region '.\Public\Test-AllowedGroupMember.ps1' 0
# TEST (maybe split)
<#
  .DESCRIPTION
  This cmdlet is used to check to see if a specific user belongs to a group that is passed
  .PARAMETER groupList
  Array of the groups to check
  .PARAMETER domain
  If active directory, what domain to check. If you use this, it ignores any of the Az parameters
  .PARAMETER AzAppRegistration
  The client id of the azure app registration working under
  .PARAMETER AzTenant
  The directory id for the Azure AD tenant
  .PARAMETER AzSecret
  The client secret used to connect to MS Graph
  .EXAMPLE
  Check for a specific user in active directory
    Test-AllowedGroupMember -userPrincipalName <UPN> -groupList @("GROUPNAME") -domain <DOMAIN>
  Check for a specific user in Azure AD group
    Test-AllowedGroupMember -userPrincipalName <UPN> -groupList @("GROUPNAME") -AzTenant $AzTenant -AzAppRegistration $AzAppRegistration -AzSecret $Secret
#>

function Test-AllowedGroupMember{
  [CmdletBinding()]
  [OutputType([System.Boolean])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$userPrincipalName,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][Object[]]$groupList,
    [Parameter()][string]$domain,
    [Parameter()][string]$AzAppRegistration,
    [Parameter()][string]$AzTenant,
    [Parameter()][pscredential]$AzSecret
    
  )
  # Nested function to be able to recurse through groups in Azure AD since Get-MGGroupMembers does not have this function currently
  function getNestedMembers{
    param(
      [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$groupId,
      [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$userPrincipalName
    )
    # Set found to false
    $results = $false
    # Get memberes of the group that was passed
    $members = Get-MgGroupMember -All -GroupId $groupId
    # If the username is found return true
    if($userPrincipalName -in $members.AdditionalProperties.userPrincipalName){
      return $true
    }
    # If not found, check the list for other nested groups
    else{
      $groups = $members | where-object {$_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.group"}
      # Loop through those groups those nested function
      foreach($group in $groups){
        $results = getNestedMembers -groupId $groupId -userPrincipalName $userPrincipalName
        if($results -eq $true){
          # if the results returned are true, return true.
          return $true
        }
      }
    }
  }
  # If set to query Azure AD Groups connect to MS Graph
  if($AzAppRegistration){
    # Connect to MS Graph
    $msalToken = Get-MsalToken -clientID $AzAppRegistration -clientSecret $AzSecret.Password -tenantID $AzTenant
    Connect-MgGraph -AccessToken $msalToken.AccessToken | Out-Null
  }
  foreach($group in $groupList){
    try{
      if($domain){
        # Get all the members and nested members of the group in Active Directory
        $members = Get-ADGroupMember -Recursive -Server $domain -Identity $group -ErrorAction SilentlyContinue  | where-object {$_.objectClass -eq 'User'} | Get-ADUser | select-object UserPrincipalName
        # Check to see if the list contains the expected UPN and if return true
        if($members.UserPrincipalName -contains $userPrincipalName){
          return $true
        }        
      }
      else{
        # Get the group from Azure AD
        $groups = Get-MGgroup -Filter "DisplayName eq '$($group)'"
        # Loop through if there are multiple groups with the same name
        foreach($group in $groups){
          # Get the results of the function to recurse through the groups
          $results = getNestedMembers -groupId $group.id -userPrincipalName $userPrincipalName
          # Return true if correct
          if($results -eq $true){
            return $true
          }
        }
      }
    }
    catch{
      throw "An error occured while processing group $($group) : $($Error[0])"
    }
  }
  return $false
}
#EndRegion '.\Public\Test-AllowedGroupMember.ps1' 96
#Region '.\Public\Test-GoogleAccessToken.ps1' 0
function Test-GoogleAccessToken{
  [CmdletBinding()]
  param()
  if(-not $global:googleAccessToken){
    throw "Please ensure that you have called Get-GoogleAccessToken cmdlet"
  }
  try{
    $uri = "https://oauth2.googleapis.com/tokeninfo?access_token=$($Global:googleAccessToken)"
    $tokenDetails = Invoke-RestMethod -Method "GET" -URI $uri -StatusCodeVariable statusCode
    if([int]$tokenDetails.expires_in -gt 900){
      Write-Verbose "Token is valid for more than 15 minutes, not getting new token."
      return $true
    }
    else{
      Write-Verbose "Token is valid for less than 15 minutes, getting new token."
      return $false
    }
  }
  catch{
    Write-Verbose "Unable to check token. Marking as needing refresh."
    return $false
  }

  "hello"
}
#EndRegion '.\Public\Test-GoogleAccessToken.ps1' 26
#Region '.\Public\Test-GraphAcessToken.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is tests to see if the passed variable is not null, and expires in less than 10 minutes
  .PARAMETER token
  The current access tokenb variable
#>

function Test-GraphAcessToken{
  [CmdletBinding()]
  param(
    [Parameter()][System.Object]$token
  )
  if(!$token){
    return $false
  }
  $expiryTime = $token.ExpiresOn - (Get-Date)
  if($expiryTime.Minutes -lt 10){
    return $false
  }
  else{
    return $true
  }
}
#EndRegion '.\Public\Test-GraphAcessToken.ps1' 23
#Region '.\Public\Test-SamAccountName.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to check Active Directory for a valid samAccountName when creating/changing user.
  .PARAMETER samAccountName
  The account name we want to test for
  .PARAMETER server
  The server that we want to test against
  .EXAMPLE
  Check for a specific samAccountName
    Test-SamAccountName -samAccountName <NAME> -server <SERVER>
#>

function Test-SamAccountName{
  [CmdletBinding()]
  [OutputType([System.String],[System.Boolean])]
  param(
    [Parameter(Mandatory = $true)]$samAccountName,
    [Parameter(Mandatory = $true)]$server    
  )
  # Default Addition at the end of the name if it exists.
  $postFix = 2
  # Loop through to try to find a valid samAccountName or fail if loops too many times
  do{
    try{
      # Check to see if the user already exists.
      Get-ADUser -Identity $samAccountName -Server $server | Out-Null
      # If it does exist, then add the postfix
      if($postFix -eq 2){
        $samAccountName = "$($samAccountName)$($postFix)"
      }
      # If postfix is greater than default, then remove it (as we max at 9) to add the new postfix
      else{
        $samAccountName = "$($samAccountName.substring(0,$samAccountName.length -1))$($postFix)"
      } 
    }
    # If the account doesn't exist, return the samAccountName as good
    catch [Microsoft.ActiveDirectory.Management.ADIdentityResolutionException] {
      return $samAccountName
    }
    catch {
      throw $Error[0]
    }
    $postFix++
  }while($postFix -lt 10)  
  # Return false if we couldn't find a valid samAccountName we could use
  return $false  
}
#EndRegion '.\Public\Test-SamAccountName.ps1' 47
#Region '.\Public\Update-TopdeskAsset.ps1' 0
<#
  .DESCRIPTION
  This cmdlet will update an asset
  .PARAMETER unid
  The unid of the asset that we want to update
  .PARAMETER update
  A hashtabe of the values for the change
  .EXAMPLE
#>

function Update-TopdeskAsset(){
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$unid,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][hashtable]$update
  )
  # Confirm we have a valid graph token
  if(!$global:topdeskAccessToken){
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }      
  # Base URI for subcategory data
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/assetmgmt/assets/$($unid)" 
  try{
    $results = Invoke-RestMethod -Method Post -Uri $uri -Headers $global:topdeskAccessToken -body ($update | ConvertTo-Json) -StatusCodeVariable statusCode
  }
  catch{
    throw $Error[0]
  }
  return $results
}
#EndRegion '.\Public\Update-TopdeskAsset.ps1' 30