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-PInvokeType.ps1' 0
function Add-PInvokeType {
  [cmdletbinding()]
  param()
  #region LSAUtil
  # C# Code to P-invoke LSA functions.
  # This code is copied from PInvoke.net
  # http://www.pinvoke.net/default.aspx/advapi32.lsaretrieveprivatedata

  Add-Type @"
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
 
namespace PInvoke.LSAUtil {
    public class LSAutil {
        [StructLayout (LayoutKind.Sequential)]
        private struct LSA_UNICODE_STRING {
            public UInt16 Length;
            public UInt16 MaximumLength;
            public IntPtr Buffer;
        }
 
        [StructLayout (LayoutKind.Sequential)]
        private struct LSA_OBJECT_ATTRIBUTES {
            public int Length;
            public IntPtr RootDirectory;
            public LSA_UNICODE_STRING ObjectName;
            public uint Attributes;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;
        }
 
        private enum LSA_AccessPolicy : long {
            POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
            POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
            POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
            POLICY_TRUST_ADMIN = 0x00000008L,
            POLICY_CREATE_ACCOUNT = 0x00000010L,
            POLICY_CREATE_SECRET = 0x00000020L,
            POLICY_CREATE_PRIVILEGE = 0x00000040L,
            POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
            POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
            POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
            POLICY_SERVER_ADMIN = 0x00000400L,
            POLICY_LOOKUP_NAMES = 0x00000800L,
            POLICY_NOTIFICATION = 0x00001000L
        }
 
        [DllImport ("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaRetrievePrivateData (
            IntPtr PolicyHandle,
            ref LSA_UNICODE_STRING KeyName,
            out IntPtr PrivateData
        );
 
        [DllImport ("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaStorePrivateData (
            IntPtr policyHandle,
            ref LSA_UNICODE_STRING KeyName,
            ref LSA_UNICODE_STRING PrivateData
        );
 
        [DllImport ("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaOpenPolicy (
            ref LSA_UNICODE_STRING SystemName,
            ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
            uint DesiredAccess,
            out IntPtr PolicyHandle
        );
 
        [DllImport ("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaNtStatusToWinError (
            uint status
        );
 
        [DllImport ("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaClose (
            IntPtr policyHandle
        );
 
        [DllImport ("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaFreeMemory (
            IntPtr buffer
        );
 
        private LSA_OBJECT_ATTRIBUTES objectAttributes;
        private LSA_UNICODE_STRING localsystem;
        private LSA_UNICODE_STRING secretName;
 
        public LSAutil (string key) {
            if (key.Length == 0) {
                throw new Exception ("Key lenght zero");
            }
 
            objectAttributes = new LSA_OBJECT_ATTRIBUTES ();
            objectAttributes.Length = 0;
            objectAttributes.RootDirectory = IntPtr.Zero;
            objectAttributes.Attributes = 0;
            objectAttributes.SecurityDescriptor = IntPtr.Zero;
            objectAttributes.SecurityQualityOfService = IntPtr.Zero;
 
            localsystem = new LSA_UNICODE_STRING ();
            localsystem.Buffer = IntPtr.Zero;
            localsystem.Length = 0;
            localsystem.MaximumLength = 0;
 
            secretName = new LSA_UNICODE_STRING ();
            secretName.Buffer = Marshal.StringToHGlobalUni (key);
            secretName.Length = (UInt16) (key.Length * UnicodeEncoding.CharSize);
            secretName.MaximumLength = (UInt16) ((key.Length + 1) * UnicodeEncoding.CharSize);
        }
 
        private IntPtr GetLsaPolicy (LSA_AccessPolicy access) {
            IntPtr LsaPolicyHandle;
            uint ntsResult = LsaOpenPolicy (ref this.localsystem, ref this.objectAttributes, (uint) access, out LsaPolicyHandle);
            uint winErrorCode = LsaNtStatusToWinError (ntsResult);
            if (winErrorCode != 0) {
                throw new Exception ("LsaOpenPolicy failed: " + winErrorCode);
            }
            return LsaPolicyHandle;
        }
 
        private static void ReleaseLsaPolicy (IntPtr LsaPolicyHandle) {
            uint ntsResult = LsaClose (LsaPolicyHandle);
            uint winErrorCode = LsaNtStatusToWinError (ntsResult);
            if (winErrorCode != 0) {
                throw new Exception ("LsaClose failed: " + winErrorCode);
            }
        }
 
        private static void FreeMemory (IntPtr Buffer) {
            uint ntsResult = LsaFreeMemory (Buffer);
            uint winErrorCode = LsaNtStatusToWinError (ntsResult);
            if (winErrorCode != 0) {
                throw new Exception ("LsaFreeMemory failed: " + winErrorCode);
            }
        }
 
        public void SetSecret (string value) {
            LSA_UNICODE_STRING lusSecretData = new LSA_UNICODE_STRING ();
 
            if (value.Length > 0) {
                //Create data and key
                lusSecretData.Buffer = Marshal.StringToHGlobalUni (value);
                lusSecretData.Length = (UInt16) (value.Length * UnicodeEncoding.CharSize);
                lusSecretData.MaximumLength = (UInt16) ((value.Length + 1) * UnicodeEncoding.CharSize);
            } else {
                //Delete data and key
                lusSecretData.Buffer = IntPtr.Zero;
                lusSecretData.Length = 0;
                lusSecretData.MaximumLength = 0;
            }
 
            IntPtr LsaPolicyHandle = GetLsaPolicy (LSA_AccessPolicy.POLICY_CREATE_SECRET);
            uint result = LsaStorePrivateData (LsaPolicyHandle, ref secretName, ref lusSecretData);
            ReleaseLsaPolicy (LsaPolicyHandle);
 
            uint winErrorCode = LsaNtStatusToWinError (result);
            if (winErrorCode != 0) {
                throw new Exception ("StorePrivateData failed: " + winErrorCode);
            }
        }
 
        public string GetSecret () {
            IntPtr PrivateData = IntPtr.Zero;
 
            IntPtr LsaPolicyHandle = GetLsaPolicy (LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION);
            uint ntsResult = LsaRetrievePrivateData (LsaPolicyHandle, ref secretName, out PrivateData);
            ReleaseLsaPolicy (LsaPolicyHandle);
 
            uint winErrorCode = LsaNtStatusToWinError (ntsResult);
            if (winErrorCode != 0) {
                throw new Exception ("RetreivePrivateData failed: " + winErrorCode);
            }
 
            LSA_UNICODE_STRING lusSecretData =
                (LSA_UNICODE_STRING) Marshal.PtrToStructure (PrivateData, typeof (LSA_UNICODE_STRING));
            string value = Marshal.PtrToStringAuto (lusSecretData.Buffer).Substring (0, lusSecretData.Length / 2);
 
            FreeMemory (PrivateData);
 
            return value;
        }
    }
}
"@

  #endregion
}
#EndRegion '.\Public\Add-PInvokeType.ps1' 190
#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()][ValidateNotNullOrEmpty()][string]$filepath,
    [Parameter()][ValidateNotNullOrEmpty()][string]$filename,
    [Parameter()][ValidateNotNullOrEmpty()][string]$base64,
    [Parameter()][ValidateNotNullOrEmpty()][string]$contenttype = "application/plain;charset=utf-8",
    [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://$($global:topdeskEnvironment).topdesk.net/tas/api/incidents/id/$($id)/attachments"
  if($filepath){
  # 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=`"$($filename)`"
Content-Type: $($contenttype)
Content-Transfer-Encoding: base64
 
$($fileContentEncoded)
--BOUNDARY--"

  }
  else{
    $body = "--BOUNDARY
    Content-Disposition: form-data; name=`"file`"; filename=`"$($filename)`"
Content-Type: application/pdf
Content-Transfer-Encoding: base64
 
$($base64)
--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' 52
#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-AutorunRegKeys.ps1' 0
function Get-AutorunRegKeys {
  [cmdletbinding()]
  param(
    [parameter(Mandatory = $true)][string]$Name,
    [parameter()][string]$UserName = $null,
    [parameter()][switch]$runOnce
  )
  $forceload = $false
  # Get a list of all the user profiles on the machine
  $ProfileList = Get-ChildItem Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -notlike "*_Classes" -and $_.PSChildName -notin ("S-1-5-18", "S-1-5-19", "S-1-5-20") }
  $UserList = foreach ($UserKey in $ProfileList) {
    @{
      ProfileKey  = $UserKey | Where-Object { $_.name -like "*" + $UserKey.PSChildName + "*" }
      UserName    = try { ((([system.security.principal.securityidentIfier]$UserKey.PSChildName).Translate([System.Security.Principal.NTAccount])).ToString()).substring(3) } catch { continue };
      SID         = $UserKey.PSChildName
      ProfilePath = Get-ItemProperty $UserKey.PSPath | Select-Object -ExpandProperty ProfileImagePath
    }
  } 
  if($null -ne $Username -and $UserName -ne ""){
    $SID = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).SID
    $baseprofile = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).ProfilePath
    if($runOnce.IsPresent){
      $registryPath = "REGISTRY::HKEY_USERS\$($SID)\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    }
    else{
      $registryPath = "REGISTRY::HKEY_USERS\$($SID)\Software\Microsoft\Windows\CurrentVersion\Run"
    }
    if(-not (Test-Path -Path $registryPath)){
      $hivepath = Join-Path -Path $baseprofile -ChildPath "NTUSER.DAT"
      reg Load "HKU\$($SID)" "$($hivepath)" | Out-Null
      $forceload = $true
      if(-not (Test-Path -Path  $registryPath)){
        throw "Unable to load hive for user: $($UserName)"
      }
    }
  }
  else{
    if($runOnce.IsPresent){
      $registryPath = "REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    }
    else{
      $registryPath = "REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run"
    }    
  } 
  $Keys = (Get-ItemProperty -Path $registryPath).psobject.properties | Where-Object { $_.Name -eq $Name }
  if($forceload){
    [gc]::Collect()
    reg unload "HKU\$($SID)" | Out-Null    
  }
  return $Keys
}
#EndRegion '.\Public\Get-AutorunRegKeys.ps1' 52
#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-DSREGCMDStatus.ps1' 0
<#
  .DESCRIPTION
  This is designed to parse the dsregcmd command to usable data. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross
#>

function Get-DSREGCMDStatus {
  [cmdletbinding()]
  param(
    [parameter(HelpMessage = "Use to add /DEBUG to DSREGCMD")][switch]$bDebug
  )
  try {
    Write-Output "Calling DSREGCMDSTATUS"
    $cmdArgs = if ($bDebug) { "/STATUS", "/DEBUG" } else { "/STATUS" }
    $DSREGCMDStatus = & DSREGCMD $cmdArgs
    $DSREGCMDEntries = [PSCustomObject]@{}
    if ($DSREGCMDStatus) {
      for ($i = 0; $i -le $DSREGCMDStatus.Count ; $i++) {
        if ($DSREGCMDStatus[$i] -like "| *") {
          $GroupName = $DSREGCMDStatus[$i].Replace("|", "").Trim().Replace(" ", "")
          $Member = @{
            MemberType = "NoteProperty"
            Name       = $GroupName
            Value      = $null
          }
          $DSREGCMDEntries | Add-Member @Member
          $i++ #Increment to skip next line with +----
          $GroupEntries = [PSCustomObject]@{}
          do {
            $i++
            if ($DSREGCMDStatus[$i] -like "*::*") {
              $DiagnosticEntries = $DSREGCMDStatus[$i] -split "(^DsrCmd.+(?=DsrCmd)|DsrCmd.+(?=\n))" | Where-Object { $_ -ne '' }
              foreach ($Entry in $DiagnosticEntries) {
                $EntryParts = $Entry -split "(^.+?::.+?: )" | Where-Object { $_ -ne '' }
                $EntryParts[0] = $EntryParts[0].Replace("::", "").Replace(": ", "")
                if ($EntryParts) {
                  $Member = @{
                    MemberType = "NoteProperty"
                    Name       = $EntryParts[0].Trim().Replace(" ", "")
                    Value      = $EntryParts[1].Trim()
                  }
                  $GroupEntries | Add-Member @Member
                  $Member = $null
                }
              }
            }
            elseif ($DSREGCMDStatus[$i] -like "* : *") {
              $EntryParts = $DSREGCMDStatus[$i] -split ':'
              if ($EntryParts) {
                $Member = @{
                  MemberType = "NoteProperty"
                  Name       = $EntryParts[0].Trim().Replace(" ", "")
                  Value      = if ($EntryParts.Count -gt 2) {
                                                  ( $EntryParts[1..(($EntryParts.Count) - 1)] -join ":").Split("--").Replace("[ ", "").Replace(" ]", "").Trim()
                  }
                  else {
                    $EntryParts[1].Trim()
                  }
                }
                $GroupEntries | Add-Member @Member
                $Member = $null
              }
            }
                  
          } until($DSREGCMDStatus[$i] -like "+-*" -or $i -eq $DSREGCMDStatus.Count)
          $DSREGCMDEntries.$GroupName = $GroupEntries
        }
      }
      return $DSREGCMDEntries
    }
    else {
      return "No Status Found"
    }
  }
  catch {
    throw $_
  }
}
#EndRegion '.\Public\Get-DSREGCMDStatus.ps1' 77
#Region '.\Public\Get-EntraDeviceCertificate.ps1' 0
<#
  .DESCRIPTION
  This is designed to get the device certificate for Entra that is enrolled. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross
#>

function Get-EntraDeviceCertificate {
  [CmdletBinding()]
  [OutputType([X509Certificate])]
  param (
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][Object]$dsregcmdStatus
  )
  try {
    Write-Host "Getting Azure AD Device Certificate"
    #Get best cert from DSRegCmd
    $Thumbprint = $dsregcmdstatus.DeviceDetails.Thumbprint
    #Get the local cert that matches the DSRegCMD Cert
    $Certs = Get-ChildItem -Path Cert:\LocalMachine\My 
    $Cert = $Certs | Where-Object { $_.Thumbprint -eq $dsregcmdstatus.DeviceDetails.Thumbprint }
    if ($Cert.Thumbprint -eq $Thumbprint) {
      return $Cert
    }
    else {
      Write-Output "No valid Entra Device Cert Found."
    }
  }
  catch {
    throw $_
  }
}
#EndRegion '.\Public\Get-EntraDeviceCertificate.ps1' 29
#Region '.\Public\Get-EntraIDDeviceID.ps1' 0
<#
  .DESCRIPTION
  This is designed to get the entra id device id. Originally from https://azuretothemax.net/log-analytics-index/
#>

function Get-EntraIDDeviceID {
  [CmdletBinding()]
  param()  
  # Define Cloud Domain Join information registry path
  $EntraIDJoinInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo"

  # Retrieve the child key name that is the thumbprint of the machine certificate containing the device identifier guid
  $EntraIDJoinInfoThumbprint = Get-ChildItem -Path $EntraIDJoinInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName"
  if ($EntraIDJoinInfoThumbprint -ne $null) {
    # Retrieve the machine certificate based on thumbprint from registry key
    $EntraIDJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $EntraIDJoinInfoThumbprint }
    if ($EntraIDJoinCertificate -ne $null) {
      # Determine the device identifier from the subject name
      $EntraIDDeviceID = ($EntraIDJoinCertificate | Select-Object -ExpandProperty "Subject") -replace "CN=", ""
      # Convert upper to lowercase.
      $EntraIDDeviceID = "$($EntraIDDeviceID)".ToLower()
      # Handle return value
      return $EntraIDDeviceID
    }
    else {
      #If no certificate was found, locate it by Common Name instead of Thumbprint. This is likely a CPC or similar.
      $EntraIDJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -like "CN=($EntraIDJoinInfoThumbprint)" }
      if ($EntraIDJoinCertificate -ne $null) {
        # Cert is now found, extract Device ID from Common Name
        $EntraIDDeviceID = ($EntraIDJoinCertificate | Select-Object -ExpandProperty "Subject") -replace "CN=", ""
        # Convert upper to lowercase.
        $EntraIDDeviceID = "$($EntraIDDeviceID)".ToLower()
        # Handle return value
        return $EntraIDDeviceID
      }
      else {
        # Last ditch effort, try and use the ThumbPrint (reg key) itself.
        $EntraIDDeviceID = $EntraIDJoinInfoThumbprint
        # Convert upper to lowercase.
        $EntraIDDeviceID = "$($EntraIDDeviceID)".ToLower()
        return $EntraIDDeviceID
      }
    }
  }
}
#EndRegion '.\Public\Get-EntraIDDeviceID.ps1' 45
#Region '.\Public\Get-EntraIDJoinDate.ps1' 0
<#
  .DESCRIPTION
  This is designed to get the entra id join date. Originally from https://azuretothemax.net/log-analytics-index/
#>

function Get-EntraIDJoinDate {
  [CmdletBinding()]
  param()
  # Define Cloud Domain Join information registry path
  $EntraIDJoinInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo"
  # Retrieve the child key name that is the thumbprint of the machine certificate containing the device identifier guid
  $EntraIDJoinInfoThumbprint = Get-ChildItem -Path $EntraIDJoinInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName"
  if ($EntraIDJoinInfoThumbprint -ne $null) {
    # Retrieve the machine certificate based on thumbprint from registry key
    $EntraIDJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $EntraIDJoinInfoThumbprint }
    if ($EntraIDJoinCertificate -ne $null) {
      # Determine the device identifier from the subject name
      $EntraIDJoinDate = ($EntraIDJoinCertificate | Select-Object -ExpandProperty "NotBefore") 
      # Handle return value
      return $EntraIDJoinDate
    }
    if ($EntraIDJoinCertificate -eq $null) {
      $EntraIDJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -eq "CN=$($EntraIDJoinInfoThumbprint)" }
      $EntraIDJoinDate = ($EntraIDJoinCertificate | Select-Object -ExpandProperty "NotBefore") 
      return $EntraIDJoinDate
    }
  }
}
#EndRegion '.\Public\Get-EntraIDJoinDate.ps1' 28
#Region '.\Public\Get-EntraIDRegistrationCertificateThumbprint.ps1' 0
<#
  .SYNOPSIS
      Get the thumbprint of the certificate used for Azure AD device registration.
   
  .DESCRIPTION
      Get the thumbprint of the certificate used for Azure AD device registration.
   
  .NOTES
      Author: Nickolaj Andersen
      Contact: @NickolajA
      Created: 2021-06-03
      Updated: 2021-06-03
   
      Version history:
      1.0.0 - (2021-06-03) Function created
      1.0.1 - (2023-05-10) Max Updated for Cloud PCs which don't have their thumbprint as their JoinInfo key name.
  #>

function Get-EntraIDRegistrationCertificateThumbprint {
  [CmdletBinding()]
  param()  
  # Define Cloud Domain Join information registry path
  $EntraIDJoinInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo"
  # Retrieve the child key name that is the thumbprint of the machine certificate containing the device identifier guid
  $EntraIDJoinInfoThumbprint = Get-ChildItem -Path $EntraIDJoinInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName"
  # Check for a cert matching that thumbprint
  $EntraIDJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $EntraIDJoinInfoThumbprint }
  if ($EntraIDJoinCertificate -ne $null) {
    # if a matching cert was found tied to that reg key (thumbprint) value, then that is the thumbprint and it can be returned.
    $EntraIDThumbprint = $EntraIDJoinInfoThumbprint
    # Handle return value
    return $EntraIDThumbprint
  }
  else {
    # If a cert was not found, that reg key was not the thumbprint but can be used to locate the cert as it is likely the Azure ID which is in the certs common name.
    $EntraIDJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -like "CN=$($EntraIDJoinInfoThumbprint)" }
    #Pull thumbprint from cert
    $EntraIDThumbprint = $EntraIDJoinCertificate.Thumbprint
    # Handle return value
    return $EntraIDThumbprint
  }
}
#EndRegion '.\Public\Get-EntraIDRegistrationCertificateThumbprint.ps1' 42
#Region '.\Public\Get-EntraIDTenantID.ps1' 0
<#
  .DESCRIPTION
  This is designed to get the entra tenant id that device belogs too. Originally from https://azuretothemax.net/log-analytics-index/
#>

function Get-EntraIDTenantID{
  [CmdletBinding()]
  param()  
  # Cloud Join information registry path
  $EntraIDTenantInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\TenantInfo"
  # Retrieve the child key name that is the tenant id for EntraID
  $EntraIDTenantID = Get-ChildItem -Path $EntraIDTenantInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName"
  return $EntraIDTenantID  
}
#EndRegion '.\Public\Get-EntraIDTenantID.ps1' 14
#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-GraphAccessPackageAssignments.ps1' 0
function Get-GraphAccessPackageAssignments{
  [cmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$displayName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$accessPackageId,
    [Parameter()][ValidateNotNullOrEmpty()][string]$groupid,
    [Parameter()][ValidateNotNullOrEmpty()][string]$groupname
  )  
  $endpoint = "identityGovernance/entitlementManagement/accessPackageAssignmentPolicies"
  if($id){
    $endpoint = "$($endpoint)?`$filter=id eq '$id'"
  }
  elseif($displayName){
    $endpoint = "$($endpoint)?`$filter=displayName eq '$displayName'"
  }
  elseif($accessPackageId){
    $endpoint = "$($endpoint)?`$filter=accessPackageId eq '$accessPackageId'"
  }
  # Create empty list
  $List = [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) {
          if($groupid -or $groupName){
            $skipItem = $true
            if($item.requestorSettings.allowedRequestors.id -contains $groupID){
              $skipItem = $false
            }
            if($item.requestorSettings.allowedRequestors.description -contains $groupname){
              $skipItem = $false
            }
            if($item.requestApprovalSettings.approvalStages.primaryApprovers.id -contains $groupID){
              $skipItem = $false
            }
            if($item.requestApprovalSettings.approvalStages.primaryApprovers.description -contains $groupname){
              $skipItem = $false
            }                        
          }
          if($skipItem){continue}
          $List.add($item)
        }
      }
      $uri = $results."@odata.nextLink"
    }while ($null -ne $results."@odata.nextLink")
  }
  catch {
    throw "Unable to get Access Packages. $($_.Exception.Message)"
  }  
  return $List    
}
#EndRegion '.\Public\Get-GraphAccessPackageAssignments.ps1' 57
#Region '.\Public\Get-GraphAccessPackageCatalog.ps1' 0
function Get-GraphAccessPackageCatalog{
  [cmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$id,
    [Parameter()][ValidateNotNullOrEmpty()][string]$displayName
  )
  $endpoint = "identityGovernance/entitlementManagement/accessPackageCatalogs"
  if($id){
    $endpoint = "$($endpoint)?`$filter=id eq '$id'"
  }
  elseif($displayName){
    $endpoint = "$($endpoint)?`$filter=displayName eq '$displayName'"
  }
  # Create empty list
  $List = [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) {
          $List.add($item)
        }
      }
      $uri = $results."@odata.nextLink"
    }while ($null -ne $results."@odata.nextLink")
  }
  catch {
    throw "Unable to get Access Packages. $($_.Exception.Message)"
  }  
  return $List
}
#EndRegion '.\Public\Get-GraphAccessPackageCatalog.ps1' 35
#Region '.\Public\Get-GraphAccessPackages.ps1' 0
function Get-GraphAccessPackages {
  [cmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$displayName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$id
  )
  if (!$(Test-GraphAcessToken $global:graphAccessToken)) {
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  # URI Endpoint
  $endpoint = "identityGovernance/entitlementManagement/accessPackages"
  if ($id) {
    $endpoint = "$($endpoint)?`$filter=id eq '$($id)'"
  }
  elseif ($displayName) {
    $endpoint = "$($endpoint)?`$filter=displayName eq '$($displayName)'"
  }
  # Create empty list
  $List = [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) {
          $List.add($item)
        }
      }
      $uri = $results."@odata.nextLink"
    }while ($null -ne $results."@odata.nextLink")
  }
  catch {
    throw "Unable to get Access Packages. $($_.Exception.Message)"
  }  
  return $List    
}
#EndRegion '.\Public\Get-GraphAccessPackages.ps1' 39
#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()][ValidateNotNullOrEmpty()][string]$clientID,
    [Parameter()][ValidateNotNullOrEmpty()][string]$tenantID,
    [Parameter()][ValidateNotNullOrEmpty()][string]$clientSecret,
    [Parameter()][switch]$interactive,
    [Parameter()][ValidateNotNullOrEmpty()][PSCustomObject]$azToken
  )
  if($(Test-GraphAcessToken $global:graphAccessToken)){
    return $global:graphAccessToken
  }  
  Add-Type -Path "$($PSScriptRoot)\Microsoft.Identity.Client.dll" -ErrorAction SilentlyContinue | Out-Null
  [string[]]$scopes = @("https://graph.microsoft.com/.default")

  try{
    if($interactive.IsPresent){
      $clientApp = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($clientId).WithAuthority("https://login.microsoftonline.com/$tenantId").WithDefaultRedirectUri().Build()
      $authenticationResult = $clientApp.AcquireTokenInteractive($scopes).ExecuteAsync().GetAwaiter().GetResult()
    }
    elseif($clientSecret){
      $clientApp = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($clientId).WithClientSecret($clientSecret).WithAuthority("https://login.microsoftonline.com/$tenantId").Build()
      $authenticationResult = $clientApp.AcquireTokenForClient($scopes).ExecuteAsync().GetAwaiter().GetResult()
    }
    elseif($azToken){
      $authenticationResult = $azToken.PSObject.Copy()
      $authenticationResult | Add-Member -MemberType AliasProperty -Name "AccessToken" -Value "Token" -Force

    }
    $global:graphAccessToken = $authenticationResult
    return $global:graphAccessToken
  }
  catch{
    throw "Unable to generate access token. Error message: $($_)"
  }
}
#EndRegion '.\Public\Get-GraphAccessToken.ps1' 47
#Region '.\Public\Get-GraphAutopilotInformation.ps1' 0
function Get-GraphAutopilotInformation {
  [CmdletBinding()]
  param(
    [parameter()][ValidateNotNullOrEmpty()][string]$SerialNumber,
    [parameter()][ValidateNotNullOrEmpty()][string]$groupTag,
    [parameter()][ValidateNotNullOrEmpty()][string]$manufacturer,
    [parameter()][ValidateNotNullOrEmpty()][string]$model,
    [parameter()][ValidateNotNullOrEmpty()][string]$azureAdDeviceId
  )
  if (!$(Test-GraphAcessToken $global:graphAccessToken)) {
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }  
  $endpoint = "deviceManagement/windowsAutopilotDeviceIdentities"
  # Create empty list
  $filters = [System.Collections.Generic.List[PSCustomObject]]@()
  if($groupTag){
    $filters.Add("contains(groupTag,'$($groupTag)')") | Out-Null
  }
  if($SerialNumber){
    $filters.Add("contains(SerialNumber,'$($SerialNumber)')") | Out-Null
  }  
  if($manufacturer){
    $filters.Add("contains(manufacturer,'$($manufacturer)')") | Out-Null
  }   
  if($model){
    $filters.Add("contains(model,'$($model)')") | Out-Null
  }   
  if($userPrincipalName){
    $filters.Add("contains(userPrincipalName,'$($userPrincipalName)')") | Out-Null
  }  
  if($azureAdDeviceId){
    $filters.Add("contains(azureAdDeviceId,'$($azureAdDeviceId)')") | Out-Null
  }  
  # Create query string for the filter
  $filterList = $filters -join " and "  
  if($filterList){
    $endpoint = "$($endpoint)?`$filter=$($filterList)"
  }  
  # Create empty list
  $deviceList = [System.Collections.Generic.List[PSCustomObject]]@() 
  # Get Graph Headers for Call
  $headers = Get-GraphHeader 
  # Get Autopilot Devices
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  do {
    # Execute call against graph
    $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode 
    foreach($item in $results.value){
      $deviceList.Add($item) | Out-Null
    }
    # Set the URI to the nextlink if it exists
    $uri = $results."@odata.nextLink"     
  }while ($null -ne $results."@odata.nextLink")
  return $deviceList
}
#EndRegion '.\Public\Get-GraphAutopilotInformation.ps1' 56
#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-GraphIntuneAPNCertificate.ps1' 0
function Get-GraphIntuneAPNCertificate{
  [CmdletBinding()]
  param()
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # URI Endpoint
  $endpoint = "deviceManagement/applePushNotificationCertificate"
  # Get Graph Headers for Call
  $headers = Get-GraphHeader
  # Invoke Rest API
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
  # return results
  return $results
}
#EndRegion '.\Public\Get-GraphIntuneAPNCertificate.ps1' 17
#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-GraphIntuneDEPCertificate.ps1' 0
function Get-GraphIntuneDEPCertificate{
  [CmdletBinding()]
  param()
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # URI Endpoint
  $endpoint = "deviceManagement/depOnboardingSettings"
  # Get Graph Headers for Call
  $headers = Get-GraphHeader
  # Invoke Rest API
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
  # return results
  return $results.Value  
}
#EndRegion '.\Public\Get-GraphIntuneDEPCertificate.ps1' 17
#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-GraphIntuneVPPCertificate.ps1' 0
function Get-GraphIntuneVPPCertificate{
  [CmdletBinding()]
  param()
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # URI Endpoint
  $endpoint = "deviceAppManagement/vppTokens"
  # Get Graph Headers for Call
  $headers = Get-GraphHeader
  # Invoke Rest API
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode
  # return results
  return $results.Value
}
#EndRegion '.\Public\Get-GraphIntuneVPPCertificate.ps1' 17
#Region '.\Public\Get-GraphMail.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to read email from a specific mailbox
  .PARAMETER mailbox
  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]$mailbox,
    [Parameter()][ValidateNotNullOrEmpty()][string]$folderid,
    [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/$($mailbox)"
  if ($folderid) {
    $uri = "$($uri)/mailFolders/$($folderid)"
  }  
  $uri = "$($uri)/messages"
  $FilterList = [System.Collections.Generic.List[PSCustomObject]]@()
  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' 58
#Region '.\Public\Get-GraphMailAttachment.ps1' 0
function Get-GraphMailAttachment{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$mailbox,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$messageid
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  $headers = Get-GraphHeader
  $uri = "https://graph.microsoft.com/beta/users/$($mailbox)/messages/$($messageid)/attachments"
  $results = Invoke-RestMethod -Method "GET" -headers $headers -StatusCodeVariable statuscode -uri $uri
  return $results.value
}
#EndRegion '.\Public\Get-GraphMailAttachment.ps1' 15
#Region '.\Public\Get-GraphMailAttachmentContent.ps1' 0
function Get-GraphMailAttachmentContent{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$mailbox,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$messageid,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$attachmentid,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$filename
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  $headers = Get-GraphHeader  
  Invoke-RestMethod -method "GET" -uri "https://graph.microsoft.com/beta/users/$($mailbox)/messages/$($messageid)/attachments/$($attachmentid)/`$value" -Headers $headers -OutFile $filename
}
#EndRegion '.\Public\Get-GraphMailAttachmentContent.ps1' 15
#Region '.\Public\Get-GraphMailFolder.ps1' 0
function Get-GraphMailFolder{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$mailbox,
    [Parameter()][ValidateNotNullOrEmpty()][string]$foldername
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  # Get Graph Header
  $headers = Get-GraphHeader  
  # Endpoint for the folders
  $uri = "https://graph.microsoft.com/beta/users/$($mailbox)/mailFolders"
  if($foldername){
    $uri = "$($uri)?`$filter=displayName eq '$($foldername)'"
  }
  $results = Invoke-RestMethod -Method "GET" -Uri $uri  -Headers $headers
  return $results.value
}
#EndRegion '.\Public\Get-GraphMailFolder.ps1' 21
#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)"
  }
}
#EndRegion '.\Public\Get-GraphUser.ps1' 68
#Region '.\Public\Get-InstalledApplications.ps1' 0
<#
  .DESCRIPTION
  This is designed to get the get the list of applications on the system. Originally from https://azuretothemax.net/log-analytics-index/
#>

function Get-InstalledApplications {
  [CmdletBinding()]
  param(
    [Parameter()][ValidateNotNullOrEmpty()][string]$UserSid
  )
  New-PSDrive -PSProvider Registry -Name "HKU" -Root HKEY_USERS | Out-Null
  $regpath = @("HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*")
  $regpath += "HKU:\$UserSid\Software\Microsoft\Windows\CurrentVersion\Uninstall\*"
  if (-not ([IntPtr]::Size -eq 4)) {
    $regpath += "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
    $regpath += "HKU:\$UserSid\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
  }
  $propertyNames = 'DisplayName', 'DisplayVersion', 'Publisher', 'UninstallString', 'InstallDate'
  $Apps = Get-ItemProperty $regpath -Name $propertyNames -ErrorAction SilentlyContinue | . { process { if ($_.DisplayName) { $_ } } } | Select-Object DisplayName, DisplayVersion, Publisher, UninstallString, InstallDate, PSPath | Sort-Object DisplayName
  Remove-PSDrive -Name "HKU" | Out-Null
  Return $Apps
}
#EndRegion '.\Public\Get-InstalledApplications.ps1' 22
#Region '.\Public\Get-IntuneDeviceCertificate.ps1' 0
<#
  .DESCRIPTION
  This is designed to get the device certificate for Intune that is enrolled. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross
#>

function Get-IntuneDeviceCertificate {
  [CmdletBinding()]
  [OutputType([X509Certificate])]
  param (
  )
  try {
    $CertIssuer = "CN=Microsoft Intune MDM Device CA"
    $ProviderRegistryPath = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"
    $ProviderPropertyName = "ProviderID"
    $ProviderPropertyValue = "MS DM Server"
    $ProviderGUID = (Get-ChildItem -Path Registry::$ProviderRegistryPath -Recurse | ForEach-Object { if ((Get-ItemProperty -Name $ProviderPropertyName -Path $_.PSPath -ErrorAction SilentlyContinue | Get-ItemPropertyValue -Name $ProviderPropertyName -ErrorAction SilentlyContinue) -match $ProviderPropertyValue) { $_ } }).PSChildName
    $DMClientPath = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\$($ProviderGUID)\DMClient\MS DM Server"
    $IntuneDeviceId = (Get-ItemPropertyValue -Path Registry::$DMClientPath -Name "EntDMID")

    $Cert = (Get-ChildItem cert:\LocalMachine\my | where-object { $_.Issuer -in $CertIssuer -and $_.Subject -like "*$IntuneDeviceId*" })
    if ($cert) {
      return $Cert
    }
  }
  catch {
    throw $_
  }  
}
#EndRegion '.\Public\Get-IntuneDeviceCertificate.ps1' 28
#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-MerakiDevices.ps1' 0
function Get-MerakiDevices{
  [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)/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-MerakiDevices.ps1' 27
#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-PublicKeyBytesEncodedString.ps1' 0
<#
  .SYNOPSIS
      Returns the public key byte array encoded as a Base64 string, of the certificate where the thumbprint passed as parameter input is a match.
   
  .DESCRIPTION
      Returns the public key byte array encoded as a Base64 string, of the certificate where the thumbprint passed as parameter input is a match.
      The certificate used must be available in the LocalMachine\My certificate store.
 
  .PARAMETER Thumbprint
      Specify the thumbprint of the certificate.
   
  .NOTES
      Author: Nickolaj Andersen / Thomas Kurth
      Contact: @NickolajA
      Created: 2021-06-07
      Updated: 2023-05-10
   
      Version history:
      1.0.0 - (2021-06-07) Function created
      1.0.1 - (2023-05-10) Max - Updated to use X509 for the full public key with extended properties in the PEM format
 
      Credits to Thomas Kurth for sharing his original C# code.
  #>

function Get-PublicKeyBytesEncodedString {
  [CmdletBinding()]
  param(
    [parameter(Mandatory = $true, HelpMessage = "Specify the thumbprint of the certificate.")]
    [ValidateNotNullOrEmpty()]
    [string]$Thumbprint
  )
  Process {
    # Determine the certificate based on thumbprint input
    $Certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $Thumbprint }
    if ($Certificate -ne $null) {
      # Bring the cert into a X509 object
      $X509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New($Certificate)
      #Set the type of export to perform
      $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
      #Export the public cert
      $PublicKeyBytes = $X509.Export($type, "")

      # Handle return value - convert to Base64
      return [System.Convert]::ToBase64String($PublicKeyBytes)
    }
  }
}
#EndRegion '.\Public\Get-PublicKeyBytesEncodedString.ps1' 47
#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-Shortcut.ps1' 0
function Get-Shortcut{
  [CmdletBinding()]
  param(
    [parameter()][ValidateNotNullOrEmpty()][string]$Name,
    [parameter()][string]$UserName = $null,
    [parameter()][string]$OneDriveOrgName = $null,
    [parameter()][switch]$StartMenu,
    [parameter()][string]$folder    
  )
  # Get a list of all the user profiles on the machine
  $ProfileList = Get-ChildItem Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -notlike "*_Classes" -and $_.PSChildName -notin ("S-1-5-18", "S-1-5-19", "S-1-5-20") }
  $UserList = foreach ($UserKey in $ProfileList) {
    @{
      ProfileKey  = $UserKey | Where-Object { $_.name -like "*" + $UserKey.PSChildName + "*" }
      UserName    = try { ((([system.security.principal.securityidentIfier]$UserKey.PSChildName).Translate([System.Security.Principal.NTAccount])).ToString()).substring(3) } catch { continue };
      SID         = $UserKey.PSChildName
      ProfilePath = Get-ItemProperty $UserKey.PSPath | Select-Object -ExpandProperty ProfileImagePath
    }
  }
  # Determine if we should be using paths from the user's profile or the public profile
  if($null -ne $Username -and $UserName -ne ""){
    $baseprofile = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).ProfilePath
    if(-not $baseprofile){
      throw "Unable to find profile for username: $($UserName)"
    }
    $desktopPath = Join-Path -Path $baseprofile -ChildPath "Desktop\$($folder)"
    $onedrivePath = Join-Path -Path $baseprofile -ChildPath "OneDrive - $($OneDriveOrgName)\Desktop\$($folder)"
    if($null -ne $OneDriveOrgName -and (Test-Path $onedrivePath)){
      $desktopPath = $onedrivePath
    }
    $startMenuPath = Join-Path -Path $baseprofile -ChildPath "AppData\Roaming\Microsoft\Windows\Start Menu\Programs\$($folder)"
  }
  else{
    $desktopPath = Join-Path -Path $ENV:PUBLIC -ChildPath "Desktop\$($folder)"
    $startMenuPath = Join-Path -path $ENV:ALLUSERSPROFILE -ChildPath "Microsoft\Windows\Start Menu\Programs\$($folder)"
  }
  # Set the path based on if we are doing start menu or desktop
  if($startMenu.IsPresent){
    $path = $startMenuPath
  }
  else{
    $path = $desktopPath
  }
  $shortcut = Join-Path -Path $path -ChildPath "$($Name).lnk"
  if(Test-Path $shortcut){
    $obj = New-Object -ComObject WScript.Shell
    $link = $obj.CreateShortcut($shortcut)
    return $link
  }
  else{
    throw "Shortcut not found: $($shortcut)"
  }
}
#EndRegion '.\Public\Get-Shortcut.ps1' 54
#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-TopdeskKnowledgeItem.ps1' 0
function Get-TopdeskKnowledgeItem {
  [cmdletbinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter()][string]$query,
    [Parameter()][string]$fields,
    [Parameter()][int]$pageSize = 100,
    [Parameter()][int]$pageStart = 0,
    [Parameter()][switch]$all
  )
  # 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/knowledgeItems?pageStart=$($pageStart)"
  # What fields to return if we want something other than the default
  if ($fields -ne "") {
    $baseuri = "$($baseuri)&fields=$($fields)"
  }       
  # Set the first URI to query
  $uri = $baseuri
  try {
    # 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[PSCustomObject]]@()
    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.item) {
        $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 [PSCustomObject]$data
}
#EndRegion '.\Public\Get-TopdeskKnowledgeItem.ps1' 44
#Region '.\Public\Get-TopdeskKnowledgeItemImages.ps1' 0
function Get-TopdeskKnowledgeItemImages {
  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)][string]$id
  )
  # Confirm we have a valid graph token
  if (!$global:topdeskAccessToken) {
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }
  # URI for the KI endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/knowledgeItems/$($id)/images"
  try {
    $results = Invoke-RestMethod -Method GET -Uri $uri -Headers $global:topdeskAccessToken -body $body -StatusCodeVariable statusCode
  }
  catch {
    throw "Error Accessing API at $($uri). $($Error[0])"
  } 
  return $results  
}
#EndRegion '.\Public\Get-TopdeskKnowledgeItemImages.ps1' 20
#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(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$mailbox,
    [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",$item.value)
        }
      }
    }
  }
  try{
    $incident = New-TopdeskIncident @body
    foreach($email in $emails)
    {
      $tempfolder = Join-Path -Path $sourceDir -ChildPath "Temp"
      if(-not (Test-Path $tempfolder)){
        New-Item -Path $tempfolder -Force -ItemType Directory
      }
      $filename = "$(Get-Random).html"
      $tempfile = Join-Path -Path $tempfolder -ChildPath $filename
      $email.body.content | Out-File $tempFile
      Add-TopdeskIncidentAttachment -id $incident.id -filepath $tempFile -filename $filename
      Remove-Item $tempfile -Force
      if($email.hasAttachments -and $rule.saveattachments){
        $attachments = Get-GraphMailAttachment -mailbox $mailbox -messageid $email.id
        foreach($attachment in $attachments){
          Add-TopdeskIncidentAttachment -id $incident.id -base64 $attachment.contentBytes -contenttype $attachment.contentType -filename $attachment.name
        }
      }      
    }
    return $incident
  }
  catch{
    throw "Unable to create incident. $($Error[0])"
  }
}
#EndRegion '.\Public\New-AlertFactoryIncident.ps1' 101
#Region '.\Public\New-EntraIDDeviceTrustBody.ps1' 0
<#
    .SYNOPSIS
        Construct the body with the elements for a sucessful device trust validation required by a Function App that's leveraging the AADDeviceTrust.FunctionApp module.
 
    .DESCRIPTION
        Construct the body with the elements for a sucessful device trust validation required by a Function App that's leveraging the AADDeviceTrust.FunctionApp module.
 
    .EXAMPLE
        .\New-AADDeviceTrustBody.ps1
 
    .NOTES
        Author: Nickolaj Andersen
        Contact: @NickolajA
        Created: 2022-03-14
        Updated: 2023-05-14
 
        Version history:
        1.0.0 - (2022-03-14) Script created
        1.0.1 - (2023-05-10) Max - Updated to no longer use Thumbprint field, no redundant.
        1.0.2 - (2023-05-14) Max - Updating to pull the Azure AD Device ID from the certificate itself.
    #>

function New-EntraIDDeviceTrustBody {
  [CmdletBinding()]
  param()
  # Retrieve required data for building the request body
  $EntraIDDeviceID = Get-EntraIDDeviceID # Still needed to form the signature.
  $CertificateThumbprint = Get-EntraIDRegistrationCertificateThumbprint
  $Signature = New-RSACertificateSignature -Content $EntraIDDeviceID -Thumbprint $CertificateThumbprint
  $PublicKeyBytesEncoded = Get-PublicKeyBytesEncodedString -Thumbprint $CertificateThumbprint

  # Construct client-side request header
  $BodyTable = [ordered]@{
    DeviceName = $env:COMPUTERNAME
    #DeviceID = $EntraIDDeviceID - Will be pulled from the key.
    Signature  = $Signature
    #Thumbprint = $CertificateThumbprint - Will be pulled from the key.
    PublicKey  = $PublicKeyBytesEncoded
  }

  # Handle return value
  return $BodyTable
}
#EndRegion '.\Public\New-EntraIDDeviceTrustBody.ps1' 43
#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-RSACertificateSignature.ps1' 0
<#
  .SYNOPSIS
      Creates a new signature based on content passed as parameter input using the private key of a certificate determined by it's thumbprint, to sign the computed hash of the content.
   
  .DESCRIPTION
      Creates a new signature based on content passed as parameter input using the private key of a certificate determined by it's thumbprint, to sign the computed hash of the content.
      The certificate used must be available in the LocalMachine\My certificate store, and must also contain a private key.
 
  .PARAMETER Content
      Specify the content string to be signed.
 
  .PARAMETER Thumbprint
      Specify the thumbprint of the certificate.
   
  .NOTES
      Author: Nickolaj Andersen / Thomas Kurth
      Contact: @NickolajA
      Created: 2021-06-03
      Updated: 2021-06-03
   
      Version history:
      1.0.0 - (2021-06-03) Function created
 
      Credits to Thomas Kurth for sharing his original C# code.
  #>

function New-RSACertificateSignature {
  param(
    [parameter(Mandatory = $true, HelpMessage = "Specify the content string to be signed.")]
    [ValidateNotNullOrEmpty()]
    [string]$Content,

    [parameter(Mandatory = $true, HelpMessage = "Specify the thumbprint of the certificate.")]
    [ValidateNotNullOrEmpty()]
    [string]$Thumbprint
  )
  Process {
    # Determine the certificate based on thumbprint input
    $Certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $CertificateThumbprint }
    if ($Certificate -ne $null) {
      if ($Certificate.HasPrivateKey -eq $true) {
        # Read the RSA private key
        $RSAPrivateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)
        if ($RSAPrivateKey -ne $null) {
          if ($RSAPrivateKey -is [System.Security.Cryptography.RSACng]) {
            # Construct a new SHA256Managed object to be used when computing the hash
            $SHA256Managed = New-Object -TypeName "System.Security.Cryptography.SHA256Managed"
            # Construct new UTF8 unicode encoding object
            $UnicodeEncoding = [System.Text.UnicodeEncoding]::UTF8
            # Convert content to byte array
            [byte[]]$EncodedContentData = $UnicodeEncoding.GetBytes($Content)
            # Compute the hash
            [byte[]]$ComputedHash = $SHA256Managed.ComputeHash($EncodedContentData)
            # Create signed signature with computed hash
            [byte[]]$SignatureSigned = $RSAPrivateKey.SignHash($ComputedHash, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
            # Convert signature to Base64 string
            $SignatureString = [System.Convert]::ToBase64String($SignatureSigned)
            # Handle return value
            return $SignatureString
          }
        }
      }
    }
  }
}
#EndRegion '.\Public\New-RSACertificateSignature.ps1' 65
#Region '.\Public\New-Shortcut.ps1' 0
<#
  .DESCRIPTION
  This is designed to create a shortcut on the desktop. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross
#>

function New-Shortcut {
  [CmdletBinding()]
  param(
    [parameter()][ValidateNotNullOrEmpty()][string]$Name,
    [parameter()][ValidateNotNullOrEmpty()][string]$CommandLine,
    [parameter()][ValidateNotNullOrEmpty()][string]$Arguments,
    [parameter()][string]$UserName = $null,
    [parameter()][string]$OneDriveOrgName = $null,
    [parameter()][switch]$StartMenu,
    [parameter()][string]$folder
  )
  # Get a list of all the user profiles on the machine
  $ProfileList = Get-ChildItem Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -notlike "*_Classes" -and $_.PSChildName -notin ("S-1-5-18", "S-1-5-19", "S-1-5-20") }
  $UserList = foreach ($UserKey in $ProfileList) {
    @{
      ProfileKey  = $UserKey | Where-Object { $_.name -like "*" + $UserKey.PSChildName + "*" }
      UserName    = try { ((([system.security.principal.securityidentIfier]$UserKey.PSChildName).Translate([System.Security.Principal.NTAccount])).ToString()).substring(3) } catch { continue };
      SID         = $UserKey.PSChildName
      ProfilePath = Get-ItemProperty $UserKey.PSPath | Select-Object -ExpandProperty ProfileImagePath
    }
  }
  # Determine if we should be using paths from the user's profile or the public profile
  if($null -ne $Username -and $UserName -ne ""){
    $baseprofile = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).ProfilePath
    if(-not $baseprofile){
      throw "Unable to find profile for username: $($UserName)"
    }
    $desktopPath = Join-Path -Path $baseprofile -ChildPath "Desktop\$($folder)"
    $onedrivePath = Join-Path -Path $baseprofile -ChildPath "OneDrive - $($OneDriveOrgName)\Desktop\$($folder)"
    if($null -ne $OneDriveOrgName -and (Test-Path $onedrivePath)){
      $desktopPath = $onedrivePath
    }
    $startMenuPath = Join-Path -Path $baseprofile -ChildPath "AppData\Roaming\Microsoft\Windows\Start Menu\Programs\$($folder)"
  }
  else{
    $desktopPath = Join-Path -Path $ENV:PUBLIC -ChildPath "Desktop\$($folder)"
    $startMenuPath = Join-Path -path $ENV:ALLUSERSPROFILE -ChildPath "Microsoft\Windows\Start Menu\Programs\$($folder)"
  }
  # Set the path based on if we are doing start menu or desktop
  if($startMenu.IsPresent){
    $path = $startMenuPath
  }
  else{
    $path = $desktopPath
  }  
  try{
    # Create folder if it does not exist, and is set to need one
    if($folder -and -not (Test-Path -Path $path)){
      New-Item -Path $path -ItemType Directory | Out-Null
    }
    $path = Join-Path -Path $path -ChildPath "$($Name).lnk"
    $WshShell = New-Object -comObject WScript.Shell
    $Shortcut = $WshShell.CreateShortcut($Path)
    $Shortcut.TargetPath = $CommandLine
    $Shortcut.Arguments = $Arguments
    Write-host "Shortcut-Path = $Path"
    $Shortcut.Save()
  }
  catch {
    throw $_
  }  
}
#EndRegion '.\Public\New-Shortcut.ps1' 67
#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\New-TopdeskKnowledgeItem.ps1' 0
function New-TopdeskKnowledgeItem {
  [cmdletbinding()]
  param(
    [Parameter()][string]$language = "en-CA",
    [Parameter(Mandatory = $true, ParameterSetName = 'Title')][string]$title,
    [Parameter(Mandatory = $true, ParameterSetName = 'body')][PSCustomObject]$body,
    [Parameter()][string]$parent,
    [Parameter()][string]$description,
    [Parameter()][string]$content,
    [Parameter()][string]$commentsForOperators,
    [Parameter()][string]$keywords,
    [Parameter()][string]$sspVisibility = "NOT_VISIBLE",
    [Parameter()][string]$sspVisibilityFilteredOnBranches = $false,
    [Parameter()][string]$operatorVisibilityFilteredOnBranches = $false

  )  
  # Confirm we have a valid graph token
  if (!$global:topdeskAccessToken) {
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }
  # URI for the KI endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/knowledgeItems"
  if (-not $body) {
    $body = [PSCustomObject]@{
      translation = [PSCustomObject]@{
        language = $language
        content  = [PSCustomObject]@{
          title                = $title
          description          = $description
          content              = $content
          commentsForOperators = $commentsForOperators
          keywords             = $keywords
        }
      }
      visibility  = [PSCustomObject]@{
        sspVisibility                        = $sspVisibility
        sspVisibilityFilteredOnBranches      = $sspVisibilityFilteredOnBranches
        operatorVisibilityFilteredOnBranches = $operatorVisibilityFilteredOnBranches
      }
    }
    if ($parent) {
      $body | Add-Member -MemberType NoteProperty -Name "parent" -Value ([PSCustomObject]@{number = $parent }) -Force
    }
  }
  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-TopdeskKnowledgeItem.ps1' 53
#Region '.\Public\New-TopdeskKnowledgeItemImage.ps1' 0
function New-TopdeskKnowledgeItemImage {
  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)][string]$id,
    [Parameter(Mandatory = $true)][string]$imagename,
    [Parameter(Mandatory = $true)][string]$imagepath
  )
  # Confirm we have a valid graph token
  if (!$global:topdeskAccessToken) {
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }
  # URI for the KI endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/knowledgeItems/$($id)/images"
  try {
    # Get base 64 of file
    $fileContentEncoded = [convert]::ToBase64String((get-content $imagepath -AsByteStream -Raw))
    $header = $global:topdeskAccessToken.Clone()
    $header."content-type" = "multipart/form-data; boundary=BOUNDARY"
    $body = @"
--BOUNDARY
Content-Disposition: form-data; name="file"; filename="$($imagename)"
Content-Type: text/plain;charset=utf-8
Content-Transfer-Encoding: base64
 
$($fileContentEncoded)
--BOUNDARY--
"@

    $results = Invoke-RestMethod -Method POST -Uri $uri -Headers $header -body $body -StatusCodeVariable statusCode
  }
  catch {
    if ($_.Exception.statusCode -eq "Conflict") {
      Write-Verbose "Image already exists ($($imagename)). Skipping."
    }
    else {
      throw "Error Accessing API at $($uri). $($Error[0])"
    }    
    
  } 
  return $results
}
#EndRegion '.\Public\New-TopdeskKnowledgeItemImage.ps1' 41
#Region '.\Public\Remove-AutorunRegKeys.ps1' 0
<#
  .DESCRIPTION
  This is designed to remove autorun keys for the machine. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross
#>

function Remove-AutorunRegKeys {
  [cmdletbinding()]
  param (
    [parameter(Mandatory = $true)][string]$Name,
    [parameter()][string]$UserName = $null,
    [parameter()][switch]$runOnce,
    [parameter()][switch]$wildcard
  )
  $forceload = $false
  # Get a list of all the user profiles on the machine
  $ProfileList = Get-ChildItem Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -notlike "*_Classes" -and $_.PSChildName -notin ("S-1-5-18", "S-1-5-19", "S-1-5-20") }
  $UserList = foreach ($UserKey in $ProfileList) {
    @{
      ProfileKey  = $UserKey | Where-Object { $_.name -like "*" + $UserKey.PSChildName + "*" }
      UserName    = try { ((([system.security.principal.securityidentIfier]$UserKey.PSChildName).Translate([System.Security.Principal.NTAccount])).ToString()).substring(3) } catch { continue };
      SID         = $UserKey.PSChildName
      ProfilePath = Get-ItemProperty $UserKey.PSPath | Select-Object -ExpandProperty ProfileImagePath
    }
  } 
  if($null -ne $Username -and $UserName -ne ""){
    $SID = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).SID
    $baseprofile = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).ProfilePath
    if($runOnce.IsPresent){
      $registryPath = "REGISTRY::HKEY_USERS\$($SID)\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    }
    else{
      $registryPath = "REGISTRY::HKEY_USERS\$($SID)\Software\Microsoft\Windows\CurrentVersion\Run"
    }
    if(-not (Test-Path -Path $registryPath)){
      $hivepath = Join-Path -Path $baseprofile -ChildPath "NTUSER.DAT"
      reg Load "HKU\$($SID)" "$($hivepath)" | Out-Null
      $forceload = $true
      if(-not (Test-Path -Path  $registryPath)){
        throw "Unable to load hive for user: $($UserName)"
      }
    }
  }
  else{
    if($runOnce.IsPresent){
      $registryPath = "REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    }
    else{
      $registryPath = "REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run"
    }    
  }
  if($wildcard.IsPresent){
    $Keys = (Get-ItemProperty -Path $registryPath).psobject.properties | Where-Object { $_.Name -like "$($Name)*" }
  }
  else{
    $Keys = (Get-ItemProperty -Path $registryPath).psobject.properties | Where-Object { $_.Name -eq $Name }
  }
  try{
    foreach ($entry in $Keys) {
      Write-Host "Removing existing items $($entry.Name)"
      Remove-ItemProperty -Path $registryPath -Name $Entry.Name
    }
    if($forceload){
      [gc]::Collect()
      reg unload "HKU\$($SID)" | Out-Null    
    }    
  }
  catch{
    throw $_
  }
}
#EndRegion '.\Public\Remove-AutorunRegKeys.ps1' 70
#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-Shortcut.ps1' 0
function Remove-Shortcut {
  [CmdletBinding()]
  param(
    [parameter(Mandatory = $true)][string]$Name,
    [parameter()][string]$UserName = $null,
    [parameter()][string]$OneDriveOrgName = $null,
    [parameter()][switch]$StartMenu,
    [parameter()][string]$folder,
    [parameter()][switch]$wildcard
  )
  # Get a list of all the user profiles on the machine
  $ProfileList = Get-ChildItem Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -notlike "*_Classes" -and $_.PSChildName -notin ("S-1-5-18", "S-1-5-19", "S-1-5-20") }
  $UserList = foreach ($UserKey in $ProfileList) {
    @{
      ProfileKey  = $UserKey | Where-Object { $_.name -like "*" + $UserKey.PSChildName + "*" }
      UserName    = try { ((([system.security.principal.securityidentIfier]$UserKey.PSChildName).Translate([System.Security.Principal.NTAccount])).ToString()).substring(3) } catch { continue };
      SID         = $UserKey.PSChildName
      ProfilePath = Get-ItemProperty $UserKey.PSPath | Select-Object -ExpandProperty ProfileImagePath
    }
  }
  # Determine if we should be using paths from the user's profile or the public profile
  if($null -ne $Username -and $UserName -ne ""){
    $baseprofile = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).ProfilePath
    if(-not $baseprofile){
      throw "Unable to find profile for username: $($UserName)"
    }
    $desktopPath = Join-Path -Path $baseprofile -ChildPath "Desktop\$($folder)"
    $onedrivePath = Join-Path -Path $baseprofile -ChildPath "OneDrive - $($OneDriveOrgName)\Desktop\$($folder)"
    if(Test-Path $onedrivePath){
      $desktopPath = $onedrivePath
    }
    $startMenuPath = Join-Path -Path $baseprofile -ChildPath "AppData\Roaming\Microsoft\Windows\Start Menu\Programs\$($folder)"
  }
  else{
    $desktopPath = Join-Path -Path $ENV:PUBLIC -ChildPath "Desktop\$($folder)"
    $startMenuPath = Join-Path -path $ENV:ALLUSERSPROFILE -ChildPath "Microsoft\Windows\Start Menu\Programs\$($folder)"
  }
  # Set the path based on if we are doing start menu or desktop
  if($startMenu.IsPresent){
    $path = $startMenuPath
  }
  else{
    $path = $desktopPath
  }
  if($wildcard.IsPresent){
    Get-ChildItem -Path $path -Filter "$($Name)*.lnk" | Remove-Item -Force -Confirm:$false
  }
  else{
    Get-ChildItem -Path $path -Filter "$($Name).lnk" | Remove-Item -Force -Confirm:$false
  }
}
#EndRegion '.\Public\Remove-Shortcut.ps1' 52
#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\Remove-TopdeskKnowledgeItemImage.ps1' 0
function Remove-TopdeskKnowledgeItemImage {
  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)][string]$id,
    [Parameter(Mandatory = $true)][string]$imagename
  ) 
  # Confirm we have a valid graph token
  if (!$global:topdeskAccessToken) {
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }
  # URI for the KI endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/knowledgeItems/$($id)/images/$($imagename)"
  try {
    $results = Invoke-RestMethod -Method DELETE -Uri $uri -Headers $global:topdeskAccessToken -body $body -StatusCodeVariable statusCode
  }
  catch {
    throw "Error Accessing API at $($uri). $($Error[0])"
  }    
}
#EndRegion '.\Public\Remove-TopdeskKnowledgeItemImage.ps1' 20
#Region '.\Public\Remove-WindowsAutoLogon.ps1' 0
<#
  .DESCRIPTION
  This is designed to disable windows autologon functionality. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross and https://github.com/mkht/DSCR_AutoLogon
#>

function Remove-WindowsAutoLogon {
  [cmdletbinding()]
  param ()
  try {
    if (-not (Test-LocalAdmin)) {
      Write-Error ('Administrator privilege is required to execute this command')
      return
    }
    Add-PInvokeType
    Write-Output "Disabling AutoLogon"
    $WinLogonKey = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
    Set-ItemProperty -Path registry::$WinLogonKey -Name "AutoAdminLogon" -Value 0 -ErrorAction SilentlyContinue
    Remove-ItemProperty -Path registry::$WinLogonKey -Name "DefaultPassword" -ErrorAction SilentlyContinue
    Remove-ItemProperty -Path registry::$WinLogonKey -Name "DefaultUserName" -ErrorAction SilentlyContinue
    $private:LsaUtil = New-Object PInvoke.LSAUtil.LSAutil -ArgumentList "DefaultPassword"
    if ($LsaUtil.GetSecret()) {
      $LsaUtil.SetSecret($null) #Clear existing password
    }
    Write-Verbose ('Auto logon has been disabled')
  }
  catch {
    throw $_
  }
}
#EndRegion '.\Public\Remove-WindowsAutoLogon.ps1' 29
#Region '.\Public\Send-GraphMailMessage.ps1' 0
function Send-GraphMailMessage{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$from,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$subject,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$message,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string[]]$to,
    [Parameter()][ValidateNotNullOrEmpty()][string[]]$cc,
    [Parameter()][ValidateNotNullOrEmpty()][string[]]$bcc,
    [Parameter()][ValidateSet("html","text")][string]$contenttype = "html",
    [Parameter(Mandatory = $false)][switch]$savetosentitems
  )
  # Confirm we have a valid graph token
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }
  $mailbody = @{
    "message" = @{
      "subject" = $subject
      "body" = @{
        "contentType" = $contenttype
        "content" = $message
      }
    }
    "saveToSentItems" = $savetosentitems.IsPresent
  }
  # Loop through recipients and add them as appropriate
  $mailto = [System.Collections.Generic.List[Hashtable]]@()
  $mailcc = [System.Collections.Generic.List[Hashtable]]@()
  $mailbcc = [System.Collections.Generic.List[Hashtable]]@()
  foreach($item in $to){
    $obj = @{
      "emailAddress" = @{
        "address" = $item
      }
    }
    $mailto.add($obj) | Out-Null
  }
  foreach($item in $cc){
    $obj = @{
      "emailAddress" = @{
        "address" = $item
      }
    }
    $mailcc.add($obj) | Out-Null
  }
  foreach($item in $bcc){
    $obj = @{
      "emailAddress" = @{
        "address" = $item
      }
    }
    $mailbcc.add($obj) | Out-Null
  }    
  $mailbody.message.add("toRecipients",$mailto)
  if($mailcc){
    $mailbody.message.add("ccRecipients",$mailcc)
  }
  if($mailbcc){
    $mailbody.message.add("bccRecipients",$mailbcc)
  }
  # Mail endpoint
  $uri = "https://graph.microsoft.com/beta/users/$($from)/sendMail"
  # Get Graph Header
  $headers = Get-GraphHeader  
  # Send Email
  Invoke-RestMethod -Method POST -Uri $uri -Headers $headers -Body ($mailbody | ConvertTo-Json -Depth 5) -StatusCodeVariable statuscode
  return $statuscode
}
#EndRegion '.\Public\Send-GraphMailMessage.ps1' 70
#Region '.\Public\Send-TeamsMessage.ps1' 0
function Send-TeamsMessage{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$webhook,
    [Parameter()][ValidateNotNullOrEmpty()][string]$text,
    [Parameter()][ValidateNotNullOrEmpty()][string]$summary,
    [Parameter()][ValidateNotNullOrEmpty()][string]$themeColor,
    [Parameter()][ValidateNotNullOrEmpty()][string]$title,
    [Parameter()][ValidateNotNullOrEmpty()][string]$activitytitle,
    [Parameter()][ValidateNotNullOrEmpty()][string]$activitysubtitle,
    [Parameter()][ValidateNotNullOrEmpty()][string]$activityimageuri,
    [Parameter()][ValidateNotNullOrEmpty()][string]$activitytext,
    [Parameter()][ValidateNotNullOrEmpty()][hashtable]$facts
  )
  $card = @{
    "@type" = "MessageCard"
    "@context" = "https://schema.org/extensions"
  }
  if($summary){
    $card.Add("summary",$summary) | Out-Null
  }
  else{
    $card.Add("text",$text) | Out-Null
  }
  if($themeColor){$card.Add("themeColor",$themeColor) | Out-Null}
  if($title){$card.Add("title",$title) | Out-Null}

  if($activitytitle -or $activitysubtitle -or $activityimageuri -or $activitytext -or $facts){
    $section = @{}
    if($activitytitle){
      $section.Add("activitytitle",$activitytitle) | Out-Null
    }
    if($activitysubtitle){
      $section.Add("activitysubtitle",$activitysubtitle) | Out-Null
    }    
    if($activityimageuri){
      $section.Add("activityImage",$activityimageuri) | Out-Null
    }   
    if($activitytext){
      $section.Add("text",$activitytext) | Out-Null
    }       
    if($facts){
      $messageFacts = [System.Collections.Generic.List[Hashtable]]@()
      foreach($item in $facts.GetEnumerator()){
        $obj = @{
          "name" = $item.key
          "value" = $item.value
        }
        $messageFacts.Add($obj) | Out-Null
      }
      $section.Add("facts",$messageFacts) | Out-Null
    }
    $card.Add("sections",@($section)) | Out-Null
  }
  Invoke-RestMethod -uri $webhook -Method Post -body ($card | ConvertTo-Json -depth 5) -ContentType 'application/json' | Out-Null
}
#EndRegion '.\Public\Send-TeamsMessage.ps1' 57
#Region '.\Public\Set-AutorunRegKeys.ps1' 0
<#
  .DESCRIPTION
  This is designed to add autorun keys for the machine. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross
#>

function Set-AutorunRegKeys {
  [cmdletbinding()]
  param(
    [parameter(Mandatory = $true)][string]$Name,
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$CommandLine,
    [parameter()][string]$UserName = $null,
    [parameter()][switch]$runOnce
  )
  $forceload = $false
  # Get a list of all the user profiles on the machine
  $ProfileList = Get-ChildItem Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -notlike "*_Classes" -and $_.PSChildName -notin ("S-1-5-18", "S-1-5-19", "S-1-5-20") }
  $UserList = foreach ($UserKey in $ProfileList) {
    @{
      ProfileKey  = $UserKey | Where-Object { $_.name -like "*" + $UserKey.PSChildName + "*" }
      UserName    = try { ((([system.security.principal.securityidentIfier]$UserKey.PSChildName).Translate([System.Security.Principal.NTAccount])).ToString()).substring(3) } catch { continue };
      SID         = $UserKey.PSChildName
      ProfilePath = Get-ItemProperty $UserKey.PSPath | Select-Object -ExpandProperty ProfileImagePath
    }
  } 
  if($null -ne $Username -and $UserName -ne ""){
    $SID = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).SID
    $baseprofile = ($UserList | Where-Object {$_.UserName -like "*$($UserName)*"}).ProfilePath
    if($runOnce.IsPresent){
      $registryPath = "REGISTRY::HKEY_USERS\$($SID)\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    }
    else{
      $registryPath = "REGISTRY::HKEY_USERS\$($SID)\Software\Microsoft\Windows\CurrentVersion\Run"
    }
    if(-not (Test-Path -Path $registryPath)){
      $hivepath = Join-Path -Path $baseprofile -ChildPath "NTUSER.DAT"
      reg Load "HKU\$($SID)" "$($hivepath)" | Out-Null
      $forceload = $true
      if(-not (Test-Path -Path  $registryPath)){
        throw "Unable to load hive for user: $($UserName)"
      }
    }
  }
  else{
    if($runOnce.IsPresent){
      $registryPath = "REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    }
    else{
      $registryPath = "REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run"
    }    
  } 
  New-ItemProperty -Path $registryPath -Name $Name -Value $CommandLine
  if($forceload){
    [gc]::Collect()
    reg unload "HKU\$($SID)" | Out-Null    
  }
}
#EndRegion '.\Public\Set-AutorunRegKeys.ps1' 56
#Region '.\Public\Set-GraphAutopilotDeviceAssignment.ps1' 0
function Set-GraphAutopilotInformation {
  param (
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$deviceId,
    [Parameter()][ValidateNotNullOrEmpty()][string]$userPrincipalName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$groupTag,
    [Parameter()][ValidateNotNullOrEmpty()][string]$deviceName
  )
  if (!$(Test-GraphAcessToken $global:graphAccessToken)) {
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }   
  $endpoint = "deviceManagement/windowsAutopilotDeviceIdentities/$($deviceId)/UpdateDeviceProperties"
  $headers = Get-GraphHeader
  $body = @{
  }
  if($userPrincipalName){
    $body.Add("userPrincipalName",$userPrincipalName) | Out-Null
  }
  if($groupTag){
    $body.Add("groupTag",$groupTag) | Out-Null
  }
  if($deviceName){
    $body.Add("displayName",$deviceName) | Out-Null
  }  
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  if($body.count -gt 0){
    Invoke-RestMethod -Method "POST" -URI $uri -Headers $headers -Body ($body | ConvertTo-Json) -StatusCodeVariable "statusCode"
  }
}
#EndRegion '.\Public\Set-GraphAutopilotDeviceAssignment.ps1' 29
#Region '.\Public\Set-GraphIntuneDevicePrimaryUser.ps1' 0
function Set-GraphIntuneDevicePrimaryUser{
  param(
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$deviceId,
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$userId
  )
  if (!$(Test-GraphAcessToken $global:graphAccessToken)) {
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  $endpoint = "deviceManagement/managedDevices/$($deviceId)/users/`$ref"
  $headers = Get-GraphHeader
  $Body = @{
    "@odata.id" = "https://graph.microsoft.com/beta/users/$($userId)"
  }
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  Invoke-RestMethod -method "POST" -Uri $uri -Headers $headers -body ($body | ConvertTo-JSON) -StatusCodeVariable "statusCode"
}
#EndRegion '.\Public\Set-GraphIntuneDevicePrimaryUser.ps1' 17
#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\Set-WindowsAutoLogon.ps1' 0
<#
  .DESCRIPTION
  This is designed to enable windows autologon functionality. Originally from https://github.com/AdamGrossTX/ManagedUserManagement/blob/main/ClientScripts/Set-AutoLogon.ps1 by Adam Gross and https://github.com/mkht/DSCR_AutoLogon
#>

function Set-WindowsAutoLogon {
  [cmdletBinding()]
  param(
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCredential]$Credential
  )
  try {
    if (-not (Test-LocalAdmin)) {
      Write-Error ('Administrator privilege is required to execute this command')
      return
    }
    Add-PInvokeType
    Write-Output "Enabling Autologon"
    $WinLogonKey = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
    Set-ItemProperty -Path registry::$WinLogonKey -Name "AutoAdminLogon" -Value 1 -Force
    Set-ItemProperty -Path registry::$WinLogonKey -Name "DefaultUserName" -Value $Credential.UserName -Force
    Remove-ItemProperty -Path registry::$WinLogonKey -Name "AutoLogonCount" -ErrorAction SilentlyContinue
    Write-Verbose ('Password will be encrypted')
    Remove-ItemProperty -Path registry::$WinLogonKey -Name "DefaultPassword" -ErrorAction SilentlyContinue
    $private:LsaUtil = New-Object PInvoke.LSAUtil.LSAutil -ArgumentList "DefaultPassword"
    $LsaUtil.SetSecret($Credential.GetNetworkCredential().Password)
    Write-Verbose ('Auto logon has been enabled')
  }
  catch {
    throw $_
  }
}
#EndRegion '.\Public\Set-WindowsAutoLogon.ps1' 31
#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-EntraIDDeviceRegistration.ps1' 0
<#
  .SYNOPSIS
      Determine if the device conforms to the requirement of being either Azure AD joined or Hybrid Azure AD joined.
   
  .DESCRIPTION
      Determine if the device conforms to the requirement of being either Azure AD joined or Hybrid Azure AD joined.
   
  .NOTES
      Author: Nickolaj Andersen
      Contact: @NickolajA
      Created: 2022-01-27
      Updated: 2022-01-27
   
      Version history:
      1.0.0 - (2022-01-27) Function created
  #>

function Test-EntraIDDeviceRegistration {
  [CmdletBinding()]
  param()  
  $EntraIDJoinInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo"
  if (Test-Path -Path $EntraIDJoinInfoRegistryKeyPath) {
    return $true
  }
  else {
    return $false
  }
}
#EndRegion '.\Public\Test-EntraIDDeviceRegistration.ps1' 28
#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-GraphIntuneAPNCertificate.ps1' 0
function Test-GraphIntuneAPNCertificate{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$days,
    [Parameter()][ValidateNotNullOrEmpty()][string]$emailfrom,
    [Parameter()][ValidateNotNullOrEmpty()][string]$emailto,
    [Parameter()][ValidateNotNullOrEmpty()][string]$teamswebhook
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  } 
  # Get the APN Certificate
  $APNCertificate = Get-GraphIntuneAPNCertificate
  # Times for Certificate
  $APNExpDate = $APNCertificate.expirationDateTime
  $APNIdentifier = $APNCertificate.appleIdentifier
  $APNExpShortDate = $APNExpDate.ToShortDateString()  
  # Set Status as null
  $APNExpirationStatus = $null
  # If the certificate has already expired
  if ($APNExpDate -lt (Get-Date)) {
    $APNExpirationStatus = "Apple MDM Push certificate has already expired"
  }
  else{
    $APNDaysLeft = ($APNExpDate - (Get-Date))
    if ($APNDaysLeft.Days -le $APNCertificateDays) {
      $APNExpirationStatus = "Apple MDM Push certificate expires in $($APNDaysLeft.Days) days"
    }
  }
  if($APNExpirationStatus -and $teamswebhook){
    $message = @{
      "webhook"           = $teamswebhook
      "summary"           = "Intune Apple Notification"
      "activityimageuri"  = "https://dev.azure.com/jeremyputman/71648e28-a38f-4acc-bafa-aa569ba7b3f8/_apis/git/repositories/d80cf88f-316e-4d8d-b8f5-d40311acc791/items?path=/Resources/Images/warning.png&%24format=octetStream"
      "title"             = $APNExpirationStatus
      "activitytitle"     = "Action Required!"
      "activitytext"      = "Must be renewed by IT Admin before the expiry date."
      "facts"             = [ordered]@{
                          "Connector:"    = "Apple Push Notification Certificate"
                          "Status:"       = $APNExpirationStatus
                          "AppleID:"      = $APNIdentifier
                          "Expiry Date:"  = $APNExpShortDate        
      }
    }
    Send-TeamsMessage @message | Out-Null
  }
  if($APNExpirationStatus -and $emailfrom -and $emailto){
    $message = @{
      "from" = $emailfrom
      "to" = $emailto
      "subject" = $APNExpirationStatus
      "savetosentitems" = $true
      "message" = "
      <strong style='font-size:14px;'>Action Required!</strong><br/><br/>
      <strong>Connector:</strong> Apple Push Notification Certificate<br/>
      <strong>Status:</strong> $($APNExpirationStatus)<br/>
      <strong>AppleID:</strong> $($APNIdentifier)<br/>
      <strong>Expiry Date:</strong> $($APNExpShortDate)<br/>
      "

    }
    Send-GraphMailMessage @message | Out-Null
  }
  return $APNExpirationStatus
}
#EndRegion '.\Public\Test-GraphIntuneAPNCertificate.ps1' 65
#Region '.\Public\Test-GraphIntuneDEPCertificate.ps1' 0
function Test-GraphIntuneDEPCertificate{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$days,
    [Parameter()][ValidateNotNullOrEmpty()][string]$emailfrom,
    [Parameter()][ValidateNotNullOrEmpty()][string]$emailto,
    [Parameter()][ValidateNotNullOrEmpty()][string]$teamswebhook
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }   
  # List of Alerts
  $alerts = [System.Collections.Generic.List[Hashtable]]@()
  # Get DEP Tokens
  $DEPTokens = Get-GraphIntuneDEPCertificate
  foreach($DEP in $DEPTokens){
    $DEPTokenDaysLeft = ($DEP.tokenExpirationDateTime - (Get-Date))
    if ($DEPTokenDaysLeft.Days -le $days) {
      $obj = [ordered]@{
        "Status" = "Apple DEP Token expires in $($DEPTokenDaysLeft.Days) days"
        "appleid" = $DEP.appleIdentifier
        "expiry" = $DEP.tokenExpirationDateTime.ToShortDateString()
        "displayname" = $DEP.tokenName
      }
      $alerts.add($obj) | Out-Null
    }
  }
  if($alerts -and $teamswebhook){
    foreach($item in $alerts){
      $message = @{
        "webhook"           = $teamswebhook
        "summary"           = "Intune Apple Notification"
        "activityimageuri"  = "https://dev.azure.com/jeremyputman/71648e28-a38f-4acc-bafa-aa569ba7b3f8/_apis/git/repositories/d80cf88f-316e-4d8d-b8f5-d40311acc791/items?path=/Resources/Images/warning.png&%24format=octetStream"
        "title"             = $item.Status
        "activitytitle"     = "Action Required!"
        "activitytext"      = "Must be renewed by IT Admin before the expiry date."
        "facts"             = [ordered]@{
                            "Connector:"    = "Apple DEP Token"
                            "Status:"       = $item.Status
                            "Display Name:" = $item.displayname
                            "AppleID:"      = $item.appleid
                            "Expiry Date:"  = $item.expiry      
        }
      }
      Send-TeamsMessage @message | Out-Null      
    }
  }
  if($alerts -and $emailfrom -and $emailto){
    foreach($item in $alerts){
      $message = @{
        "from" = $emailfrom
        "to" = $emailto
        "subject" = $item.Status
        "savetosentitems" = $true
        "message" = "
        <h2>Action Required!</h2>
        <strong>Connector:</strong> Apple DEP Token<br/>
        <strong>Status:</strong> $($item.Status)<br/>
        <strong>Display Name:</strong> $($item.displayname)<br/>
        <strong>AppleID:</strong> $($item.appleid)<br/>
        <strong>Expiry Date:</strong> $($item.expiry)<br/>
        "

      }
      Send-GraphMailMessage @message | Out-Null      
    }
  }
  return $alerts  
}
#EndRegion '.\Public\Test-GraphIntuneDEPCertificate.ps1' 69
#Region '.\Public\Test-GraphIntuneLicense.ps1' 0
function Test-GraphIntuneLicense{
  [CmdletBinding()]
  param(
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$userId
  )
  if (!$(Test-GraphAcessToken $global:graphAccessToken)) {
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }   
  $endpoint = "deviceAppManagement/managedAppStatuses('userstatus')?userId=$($userId)"
  $headers = Get-GraphHeader
  $uri = "https://graph.microsoft.com/beta/$($endpoint)"
  $result = Invoke-RestMethod -Method "GET" -URI $uri -Headers $headers -StatusCodeVariable "statusCode"
  $license = $result.content.validationStatuses | Where-Object { $_.validationName -eq 'Intune License' }
  if($license.State -eq 'Pass'){
    return $true
  }
  else{
    return $false
  }
}
#EndRegion '.\Public\Test-GraphIntuneLicense.ps1' 21
#Region '.\Public\Test-GraphIntuneVPPCertificate.ps1' 0
function Test-GraphIntuneVPPCertificate{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$days,
    [Parameter()][ValidateNotNullOrEmpty()][string]$emailfrom,
    [Parameter()][ValidateNotNullOrEmpty()][string]$emailto,
    [Parameter()][ValidateNotNullOrEmpty()][string]$teamswebhook
  )
  if(!$(Test-GraphAcessToken $global:graphAccessToken)){
    throw "Please Call Get-GraphAccessToken before calling this cmdlet"
  }   
  # List of Alerts
  $alerts = [System.Collections.Generic.List[Hashtable]]@()
  # Get VPP Tokens
  $VPPTokens = Get-GraphIntuneVPPCertificate
  foreach($VPP in $VPPTokens){
    # Check if the token is current valid
    if ($VPP.state -ne 'valid') {
      $obj = [ordered]@{
        "Status" = "Apple VPP Token is not valid, new token required"
        "appleid" = $VPP.appleId
        "expiry" = $VPP.expirationDateTime.ToShortDateString()
        "displayname" = $VPP.displayName
      }
      $alerts.add($obj) | Out-Null
    }
    else{
      $VPPTokenDaysLeft = ($VPP.expirationDateTime - (Get-Date))
      if ($VPPTokenDaysLeft.Days -le $days) {
        $obj = [ordered]@{
          "Status" = "Apple VPP Token expires in $($VPPTokenDaysLeft.Days) days"
          "appleid" = $VPP.appleId
          "expiry" = $VPP.expirationDateTime.ToShortDateString()
          "displayname" = $VPP.displayName
        }
        $alerts.add($obj) | Out-Null        
      }
    }
  }
  if($alerts -and $teamswebhook){
    foreach($item in $alerts){
      $message = @{
        "webhook"           = $teamswebhook
        "summary"           = "Intune Apple Notification"
        "activityimageuri"  = "https://dev.azure.com/jeremyputman/71648e28-a38f-4acc-bafa-aa569ba7b3f8/_apis/git/repositories/d80cf88f-316e-4d8d-b8f5-d40311acc791/items?path=/Resources/Images/warning.png&%24format=octetStream"
        "title"             = $item.Status
        "activitytitle"     = "Action Required!"
        "activitytext"      = "Must be renewed by IT Admin before the expiry date."
        "facts"             = [ordered]@{
                            "Connector:"    = "Apple VPP Token"
                            "Status:"       = $item.Status
                            "Display Name:" = $item.displayname
                            "AppleID:"      = $item.appleid
                            "Expiry Date:"  = $item.expiry      
        }
      }
      Send-TeamsMessage @message | Out-Null      
    }
  }
  if($alerts -and $emailfrom -and $emailto){
    foreach($item in $alerts){
      $message = @{
        "from" = $emailfrom
        "to" = $emailto
        "subject" = $item.Status
        "savetosentitems" = $true
        "message" = "
        <h2>Action Required!</h2>
        <strong>Connector:</strong> Apple VPP Token<br/>
        <strong>Status:</strong> $($item.Status)<br/>
        <strong>Display Name:</strong> $($item.displayname)<br/>
        <strong>AppleID:</strong> $($item.appleid)<br/>
        <strong>Expiry Date:</strong> $($item.expiry)<br/>
        "

      }
      Send-GraphMailMessage @message | Out-Null      
    }
  }
  return $alerts
}
#EndRegion '.\Public\Test-GraphIntuneVPPCertificate.ps1' 81
#Region '.\Public\Test-LocalAdmin.ps1' 0
function Test-LocalAdmin{
  [CmdletBinding()]
  param ()
  try{
    return ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')
  }
  catch {
    throw $_
  }  
}
#EndRegion '.\Public\Test-LocalAdmin.ps1' 11
#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
#Region '.\Public\Update-TopdeskKnowledgeItemTranslation.ps1' 0
function Update-TopdeskKnowledgeItemTranslation {
  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)][string]$id,
    [Parameter()][string]$language = "en-CA",
    [Parameter(Mandatory = $true, ParameterSetName = 'body')][PSCustomObject]$body,
    [Parameter(Mandatory = $true, ParameterSetName = 'Title')][string]$title,
    [Parameter()][string]$description,
    [Parameter()][string]$content,
    [Parameter()][string]$commentsForOperators,
    [Parameter()][string]$keywords
  )  
  # Confirm we have a valid graph token
  if (!$global:topdeskAccessToken) {
    throw "Please Call Connect-Topdesk before calling this cmdlet"
  }
  # URI for the KI endpoint
  $uri = "https://$($global:topdeskEnvironment).topdesk.net/tas/api/knowledgeItems/$($id)/translations/$($language)"
  if (-not $body) {
    $body = [PSCustomObject]@{
      title                = $title
      description          = $description
      content              = $content
      commentsForOperators = $commentsForOperators
      keywords             = $keywords
    }
  }
  try {
    $results = Invoke-RestMethod -Method POST -Uri $uri -Headers $global:topdeskAccessToken -body ($body | ConvertTo-Json -depth 5) -StatusCodeVariable statusCode
  }
  catch {
    throw "Error Accessing API at $($uri). $($Error[0])"
  } 
  return $results      
}
#EndRegion '.\Public\Update-TopdeskKnowledgeItemTranslation.ps1' 36