The Future of Cloud Databases: Amazon RDS for Oracle’s Latest Update

In my journey as the founder of DBGM Consulting, Inc., a firm deeply immersed in the intricacies of cloud solutions and Artificial Intelligence, staying abreast of technological advancements is not just a preference but a necessity. The recent announcement by Amazon RDS regarding its support for the January 2024 Release Update (RU) for Oracle is a development worth discussing. This enhancement underscores not only the relentless pace of cloud innovation but also its significant impact on the future of database management and migration strategies.

Understanding Amazon RDS for Oracle’s January 2024 RU

Amazon Relational Database Service (RDS) for Oracle has introduced its support for the January 2024 Release Update, a move poised to revolutionize how businesses manage their Oracle databases in the cloud. For those managing or planning to migrate their databases to Amazon RDS, this update represents a critical shift towards more seamless, secure, and efficient database operations.

  • Automated Upgrades: One of the notable features of this update is the auto minor version upgrade (AmVU) option. When enabled, it ensures that your DB instance is automatically upgraded to the latest quarterly RU within six to eight weeks of its availability. This automation is not just about convenience but also about ensuring that your systems are running on the most secure, reliable, and high-performing version available.
  • Scheduled Maintenance Window: These upgrades are scheduled to occur during a predefined maintenance window, a feature designed to minimize disruption and downtime. Understanding and planning around the Amazon RDS maintenance window is crucial for any business striving for uninterrupted service delivery.

Embracing the Shift: The Role of Updates in Cloud Strategy

As someone who has navigated the evolution of cloud technologies from various vantage points – from a Senior Solutions Architect at Microsoft to a consulting firm leader – the significance of such updates cannot be overstated. They are not merely technical enhancements but are strategic milestones that can define the efficiency, security, and scalability of your cloud infrastructure.

Incorporating the latest Oracle RUs into your cloud migration strategy or database management plan is imperative. It ensures compliance, enhances security features, and leverages the latest optimizations in database technology. For organizations looking to stay competitive, adaptive, and secure in the digital era, acknowledging and preparing for these updates is essential.

Conclusion

The January 2024 Release Update for Amazon RDS for Oracle is more than just an upgrade; it’s a testament to the dynamic nature of cloud computing and its inherent capacity for continual improvement. For consulting firms like DBGM Consulting, Inc. and businesses across sectors, adapting to these changes is part of the journey towards digital transformation. As we forge ahead, understanding, embracing, and leveraging these updates will be paramount in harnessing the full potential of cloud technologies.

Focus Keyphrase: January 2024 Release Update for Amazon RDS for Oracle

Overview

I created this application, Microsoft Intune Management Tool, to allow you to easily View the Microsoft Graph return content behind the settings applied and configured within your Intune tenant. Additionally, it has the ability to Backup your entire tenant (as JSON content data), creating special database files of the settings and where they were applied, as well as the ability to Restore those settings back to the same or a different tenant. This would be ideal in a scenario where you might want to promote settings from a Pilot tenant to your Production tenant once you have verified the changes. Finally, the tool can create an HTML Report of the settings applied to your Intune tenant to give you a quick overview of settings, users, groups, and devices associated to your environment.

Getting Your Environment Ready for the Application

This application was written in PowerShell and needs to be connected to the internet in order to run. It also requires the AzureAD module for ADAL (Azure Active Directory Authentication Libraries) authentication. It will attempt to download this module automatically if you do not already have it installed. However, feel free to get your environment ready by running:


Install-Module AzureAD

Installing the Application

Download the required files below and unzip the files into a directory of your choosing.

Once downloaded, you can optionally choose to add your user account to the invocation script inside of the Invoke-MicrosoftIntuneManagementTool.ps1 file so you do not need to enter it every time. To do this, simply uncomment the $user variable at the end of the file and add your administrative Intune tenant account:


$user = user@company.onmicrosoft.com

Running the Application

To run the application, run the Invoke-MicrosoftIntuneManagementTool.ps1 script within PowerShell and you will immediately be prompted to log into your Azure tenant:

General Use of Application

Once logged in, you will be presented with the applications main panel. The layout is meant to mimic the general layout with your Intune web console.

Each of the non-colored menu items are standard view processes and will allow you to see the fine details surrounding your Intune setup. This content in this application is gathered from the Microsoft Graph API through GET request URIs, where JSON content is returned in the HTTP response body to the application. For example, if we click Users we can see the Azure AD users associated to this tenant. To retrieve these users the application sends the request URI https://graph.microsoft.com/v1.0/users to the Microsoft Graph API, and the users are returned as JSON content, converted to a PowerShell object and the object’s displayName property is used to create the menu structure. Selecting a user allows us to view the JSON content which contains the properties set for that user, including the displayName property used to build the menu item:

Every view component in this application works in a similar manner. Through this process, you can look at the finer details of each of the areas within your Intune setup. For a look at each section, read the A Look at Each Application Section below. Your intune console interacts with Microsoft Graph in the same was as this application where a request URI is sent to Microsoft Graph and JSON content is returned to populate the site content.

Backup Your Tenant

To back up your tenant, launch the tool and select Backup Your Tenant. This will create backup entries for each of your Intune settings, policies, devices, users and groups. Contained with this backup will be a special restore .CSV file that is used specifically with this application’s restore feature. This .CSV is a small collection of the settings and their categories/request URI the setting belongs to, as well as the associated JSON content file which contains the actual backup content. To start the process, select Backup Your Entire Intune Tenant and select a folder to place the backup files and folders inside of:

Once the backup process begins, the tool will determine which Intune settings are applicable to your environment to be backed up. This is accomplished by running a GET request to Microsogt Graph against each request URI that I programmed into the application (which I gathered through the Microsoft Graph API -> Intune documentation). When a request URI has a response header containing valid JSON content, it will back it up by saving the content as a JSON file and add a line to the .CSV file.

Once the process is complete, within your selected folder will be the backed up settings, policies, devices, users and groups associated to your Intune setup. The backups are pre-arranged into category sub-folders and restore files based on where they generally are found within the Intune console:

The .CSV files are special files used for this applications restore process. Because a content restore needs be sent back to Microsoft Graph as a POST method, the specific URI associated to the content when it was retrieved with the GET method can now be conveniently retrieved from the .CSV file to build the POST request URI.

Modifying a Backup

Contained within each backup folder are the JSON content source files:

You can easily modify or view the contents of a JSON content source file if you’d like to make adjustments before the data is restored. For example, here we could go into the MobileApps directory and modify one of the application’s name and description:

Restore Your Tenant

To restore your tenant, launch the tool and select Restore Your Tenant. You can either select to restore an individual JSON content file (such as a single application or a single user), or restore a complete section of your tenant, such as in this example where we could restore all the mobile applications:

However, before a restore can take place, the JSON content may need to be modified to allow it to work in a POST method back to Microsoft Graph. See more on this process below.

Import Notes on Restoring Tenant Information

Restoring your tenant may require modification to the JSON content source file, depending on the type of content being restored. For example, the JSON application backup files contained inside the MobileApps directory contain additional properties returned from the GET response that will not work in a POST method back to the Microsoft Graph API. To import a mobile application, ONLY the following JSON key/value pairs can be kept in the file as showing in the example below. You would need to delete the other pairs from the JSON file simply by deleting their line from the file.

A properly formatted Mobile Application JSON file only contains these key/value pairs and is now ready to be POSTed back to Microsoft Graph using the applications retore feature:


{
    "@odata.type": "#microsoft.graph.androidStoreApp",
    "displayName": "Intune Managed Browser",
    "description": "Intune Managed Browser",
    "publisher": "Microsoft Corporation",
    "isFeatured": true,
    "appStoreUrl": "https://play.google.com/store/apps/details id=com.microsoft.intune.mam.managedbrowser&hl=en",
    "minimumSupportedOperatingSystem": {
      "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem",
      "v4_0": true
    }

Any applications that could not be POSTed will be skipped during the restore process with a warning message:

Restoring a Single Setting

To restore a single setting, run the backup wizard and choose Restore an Individual Setting (JSON File). This will allow you to pick an individual JSON backup file, such as a single application:

This will require you to know what URI the JSON backup needs to be POSTed to:

You can determine this by looking with the restore .CSV associated to this backup group:

Again, JSON restores by this method follow the same rules under the Import Notes on Restoring Tenant Information section above.

Create an HTML Report of Your Tenant Settings

The application has the ability to create a full HTML report of all settings, policies, users and groups within your tenant. To run the report choose Create HTML Report of Tenant Settings from the main menu. Next, select a folder where the HTML report file will be created.

During the process of report generation, you can monitor the output:

Generating the report takes about three minutes to complete, but could take longer if you have several more settings, policies, users or groups. Once the report has been generated, locate it within the folder you specified and open it in your browser. The report is intended to give you a better understanding of what general settings, policies, users and groups are configured in your tenant:

A Look at Each Application Section

This section will take a look at each of the application sections and give a brief overview of the setting, policy, user or group contained within.

Device Enrollment

The device enrollment section contains all of the policies centered on getting new devices into the Intune tenant. Device Identifiers are an IMEI or serial you have preassigned to Intune to identify your corporate devices. Windows Autopilot is used to set up and pre-configure new devices, getting them ready for productive use, allowing a device to be shipped from OEM straight to an end user. NDES (Network Device Enrolment Service) is a feature of AD Certificate Services (CS) and provides an implementation of the Simple Certification Enrolment Protocol (SCEP).

Device Compliance

The device compliance section contains all of the policies centered on getting new devices into the Intune tenant. Intune device compliance policies define the rules and settings that a device must comply with in order to be considered compliant by your Intune tenant. When you enroll a device into Intune, the Azure AD registration process happens, which updates the device properties with more information into Azure AD. One key device information is the device compliance status, which is used by conditional access policies to block or allow access to e-mail and other corporate resources. Device Management Partners include policies to coordinate device compliance from a 3rd party program, such as Jamf compliance for Apple computers.

Devices

This section lets you see what managed devices are enrolled into your Intune tenant. This differs from devices that might use some of your corporate resources through MAM-WE (Mobile Application Management Without Enrollment). These devices are consider unmanaged and would not be listed here.

Mobile Applications

This section lets you view the apps and associated policies centered on Mobile Application Management in your environment. Mobile App Categories can be used to help you sort apps to make them easier for users to find in the company portal. Managed App Policies, also known as App Protection Policies, are used to prevent company data from saving to the local storage of the device, and restrict data movement to other apps that are not protected by App protection policies.

eBooks

The Apple Volume Purchase Program (VPP) lets you purchase multiple licenses for a book that you want to distribute to users in your company. You can distribute books from the Business, or Education stores.

Condition Access

Conditional Access allow you to define conditions that prevent access to corporate data based on location, device, user state, and application sensitivity. Conditional Access settings in Intune are actually pulled from the Azure conditional access portal.

On-Premises Access

This looks at the policies centered on conditional access for Exchange on-premises based on device compliance.

Users

This section simply lists the users in your Azure Active Directory environment that are associated to your Intune tenant. Remember, after you’ve created a user, you need to assign an Intune license to that user. Without assigning them a license, they can’t enroll their device.

Groups

This section lists all of your Azure Active Directory security groups.

Intune Roles

This section lets you view your Intune roles and the users they are associated to. Intune Roles are a part of Role Based Access Controls in Intune. RBAC helps you control who can perform various Intune tasks within your organization, and who those tasks apply to.

Software Updates

Update policies for iOS let you view policies that would force your supervised iOS devices to automatically install the latest available software update.

Advanced: Adding Future Release Features to the Application

This application is based primarily around REST URI requests used by Intune to Microsoft Graph. When Microsoft adds a new feature to Intune, there will likely be a new REST URI associated to this feature. Finding the REST URI is a matter of locating it within the Microsoft Graph/Intune documentation provided by Microsoft or by finding it in your browser’s developer mode (F12) when a request to the new feature is made as show in this example:

Once a URI is obtained for a new feature, open the MicrosoftIntuneManagementToolModules.psm1 modules file. Arrays of the REST URIs are combined in the main menu function and are easily mapped toward the end of the file. For example, if we wanted to add a new Troubleshooting function, and we know the URI for it, we can go locate the Get-MicrosoftGraphIntuneTroubleshootingQueries function. All we need to do is add the new function as a line in the array creation as shown below:


function Get-MicrosoftGraphIntuneTroubleshootingQueries{

    $MicrosoftGraphIntuneTroubleshootingQueries = @()

    $MicrosoftGraphIntuneTroubleshootingQueries += [PSCustomObject] @{'Query' = 'https://graph.microsoft.com/beta/deviceManagement/troubleshootingEvents'; 'DisplayName' = 'Troubleshooting Events'};
    $MicrosoftGraphIntuneTroubleshootingQueries += [PSCustomObject] @{'Query' = 'https://graph.microsoft.com/beta/deviceManagement/newTroubleshootingArea'; 'DisplayName' = 'New Troubleshooting Area!'};

    return $MicrosoftGraphIntuneTroubleshootingQueries
}

Now when we re-run the application we will find our new URI function listed under troubleshooting, and it will also be included in the backup/restore and reporting functions. It’s that easy!

This application will automatically take any settings returned by that URI query and create a menu structure for them. Since I completely made up this URI for demonstration purposes (or if you picked one that didn’t work), we simply get the following message when trying to bring up that menu item:

The Invocation Function: Invoke-MicrosoftIntuneManagementTool.ps1


<# .Synopsis Invoke the Microsoft Intune Management Tool application. It provides basic structure for module imports and authentication. .Author David Maiolo .Notes $user variable is your InTune tenant admin account such as admin@yourcompany.onmicrosoft.com Additional documention on this program can be found at https://www.davidmaiolo.com/portfolio-item/intune-management-tool/ #>

function Invoke-MicrosoftIntuneManagementTool{

    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   [ValidateScript({$_ -like '*onmicrosoft.com*'})]
                   [string]$user
    )

    clear
    #Import Local Modules
    $ModulePath = "$PSScriptRoot\MicrosoftIntuneManagementToolModules.psm1"
    $localmodules = @("$ModulePath")
    foreach ($localmodule in $localmodules){
        if (Test-Path "$ModulePath") {
            try{
                Write-Host "Importing modules..."
                Import-Module $localmodule -Force
                }
            catch{
                Write-Error "Error: Could not import $localmodule."
                break
            }
        }else {
            Write-Error "Error: Module Path '$ModulePath' doesn't exist."
            break
        }
    }
    
    $modules = @("AzureAD")
    #Install Azure AD modules 
    foreach ($module in $modules){
        if (Get-Module -ListAvailable -Name $module) {
            Write-Host "Confirmed: $module module was already imported."
        } else {
            try{
                Write-Host "$module module does not exist. Installing $module..." -ForegroundColor Yellow
                Install-Module $module
            }catch{
                Write-Error "Could not install required module $module. Tried to run `"Install-Module $module`" but it didn't work."
                break

            }
        }
    }

    # Checking if authToken exists before running authentication
    if($global:authToken){
        #Get Universal Date and Time
        $DateTime = (Get-Date).ToUniversalTime()
        # If the authToken exists checking when it expires
        $TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes

        if($TokenExpires -le 0){
            write-host "Warning: Authentication Token expired" $TokenExpires "minutes ago." -ForegroundColor Yellow
            $global:authToken = Get-AuthToken -User $User
        }else{
            Write-Host "User $user has already been authenticated. (against Microsoft Graph)"
        }
    }else{
        $global:authToken = Get-AuthToken -User $User
    }

    #Invoke the Application
    Get-IntuneManagementToolMenu

}

#Choose Login User
#$user = "dmaiolo@stonystudio.onmicrosoft.com"
if(!($user)){$user = Read-Host "Intune Tenant Admin Account"}

#Invoke the Invoker :-)
Invoke-MicrosoftIntuneManagementTool -user $user

The Methods: MicrosoftIntuneManagementToolModules.psm1


#Main Program Function

function Get-IntuneManagementToolMenu{
<# .SYNOPSIS This function is used to call the main menu of the application. It will look to the Query functions as the structure for what should be included. .Author David Maiolo .NOTES Additional documention on this program can be found at https://www.davidmaiolo.com/portfolio-item/intune-management-tool/ #>

    $global:ApplicationName = "Microsoft Intune Management Tool"
    $global:ApplicationAuthor = "David Maiolo"
    $global:ApplicationVersion = "0.8"

    $IntuneManagementQueryCollections = @()

    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneDeviceEnrollmentQueries'; 'DisplayName' = 'Device Enrollment'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneDeviceComplianceQueries'; 'DisplayName' = 'Device Compliance'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneDeviceQueries'; 'DisplayName' = 'Devices'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneAppManagementQueries'; 'DisplayName' = 'Mobile Apps'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneEbookQueries'; 'DisplayName' = 'eBooks'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneConditionalAccessQueries'; 'DisplayName' = 'Conditional Access'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneOnPremisesQueries'; 'DisplayName' = 'On-premises Access'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphAzureUsersQueries'; 'DisplayName' = 'Users'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphAzureGroupsQueries'; 'DisplayName' = 'Groups'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneRolesQueries'; 'DisplayName' = 'Intune Roles'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneSoftwareUpdatesQueries'; 'DisplayName' = 'Software Updates'};
    $IntuneManagementQueryCollections += [PSCustomObject] @{'Querys' = 'Get-MicrosoftGraphIntuneTroubleshootingQueries'; 'DisplayName' = 'Troubleshoot'};

    try{
        #process the Main application Menu
        clear
        Write-Host "========================================="
        Write-Host "$global:ApplicationName v$global:ApplicationVersion    "
        Write-Host "========================================="
        Write-Host "Application Author: $global:ApplicationAuthor"
        #Get-IntuneManagementDashBoard
        #Write-Host "========================================="
        $MenuArray = @()
        
        for ($i=1;$i -le $IntuneManagementQueryCollections.count; $i++) {

            Write-Host "[$i] $($IntuneManagementQueryCollections[$i-1].DisplayName)"

            if($i -eq $IntuneManagementQueryCollections.count){
                $iplusone = ($i+1)
                Write-Host "[$iplusone] Create HTML Report of Tenant Settings" -ForegroundColor Blue
                $iplustwo = ($i+2)
                Write-Host "[$iplustwo] Restore Your Tenant" -ForegroundColor Cyan
                $iplusthree = ($i+3)
                Write-Host "[$iplusthree] Backup Your Tenant" -ForegroundColor Cyan
                $iplusfour = ($i+4)
                Write-Host "[$iplusfour] Exit" -ForegroundColor Yellow
            }
        }
        do {
            try {$numOk = $true;[int]$ans = Read-host "Enter Selection"}
            catch {$numOK = $false}
            } # end do 
        until (($ans -ge 1 -and $ans -le $iplusfour) -and $numOK)

        
        if ($ans -eq ($IntuneManagementQueryCollections.count+1)){
            Write-Host "Starting Backup program..."
            sleep -Milliseconds 300
            Get-IntuneTenantReport -IntuneManagementQueryCollections $IntuneManagementQueryCollections
        }elseif ($ans -eq ($IntuneManagementQueryCollections.count+2)){
            Write-Host "Starting Restore program..."
            sleep -Milliseconds 300
            Import-IntuneTenant
        }
        elseif ($ans -eq ($IntuneManagementQueryCollections.count+3)){
            Write-Host "Starting Backup program..."
            sleep -Milliseconds 300
            Export-IntuneTenant -IntuneManagementQueryCollections $IntuneManagementQueryCollections
        }
        elseif ($ans -eq ($IntuneManagementQueryCollections.count+4)){
            Write-Host "You Exited The Program."
            exit
        }else{
            $DisplayName = $($IntuneManagementQueryCollections[$ans-1].DisplayName)
            $Querys = Invoke-Expression $($IntuneManagementQueryCollections[$ans-1].Querys)
            Write-Host "Invoking $($IntuneManagementQueryCollections[$ans-1].Querys) ($($IntuneManagementQueryCollections[$ans-1].DisplayName))..."
            sleep -Milliseconds 300
            Get-MainMenu -Querys $Querys -QueryName $DisplayName -UseSubMenu
        }
    }catch{
        Write-Error "There was a problem rendering the main program menu."
        break
    }
}

function Get-IntuneManagementDashBoard{
<# .SYNOPSIS This function is used to call a small dashboard of settings. It currently is not being used but can easily added to the main menu function if desired. .Author David Maiolo .NOTES #>
    #Tenant Users
    $tenantUsers = Get-MicrosoftGraphRESTRequest -uri "https://graph.microsoft.com/v1.0/users"
    #Tenant Groups
    $tenantGroups = Get-MicrosoftGraphRESTRequest -uri "https://graph.microsoft.com/v1.0/groups"
    #Devices
    $tenantDevices = Get-MicrosoftGraphRESTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices"
    #Applications
    $tenantApplications = Get-MicrosoftGraphRESTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/detectedApps"
    

    Write-Host "Azure Users:"($tenantUsers| Measure-Object).Count
    Write-Host "Azure Groups:"($tenantGroups| Measure-Object).Count
    Write-Host "Managed Devices:"($tenantDevices| Measure-Object).Count
    Write-Host "Deteted Applications:"($tenantApplications| Measure-Object).Count

}

#Functions Specific To Microsoft Graph

function Get-MicrosoftGraphRESTRequest{
<# .SYNOPSIS This function is used to call your Microsoft Graph request of choice and return the results as an array. .Author David Maiolo .NOTES NAME: Get-MicrosoftGraphRESTRequest #>
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [string]$uri
    )

    try{

        $GraphRequestValue = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value
        if ($GraphRequestValue -eq $null){
            Write-Host "This URI is has no values defined in your tenant: $uri" -ForegroundColor Yellow
            return $false
        }
        return $GraphRequestValue

    }catch{
        <#$ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); Write-Host "Response content:`n$responseBody" -ForegroundColor Red Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" write-host break#>
        $ex = $_.Exception
        
        Write-Host "This URI is invalid in your tenant: $uri" -ForegroundColor Red
        return $false
        
        
    }

}

function Get-MicrosoftGraphURIValidation{
<# .SYNOPSIS This function is used to test a Microsoft Graph URI. It returns true if the URI query is good and false otherwise. .Author David Maiolo .NOTES NAME: Get-MicrosoftGraphRESTRequest #>
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [string]$uri
    )

    $QueryTest = Get-MicrosoftGraphRESTRequest -uri $uri

    #return true or false depending on how the query faired
    if($QueryTest){return $true}{else return $false}

}

function Get-MicrosoftGraphURIValidQuerys{
<# .SYNOPSIS This function builds a small valid query report. It used internally by the developed for debugging purposes. .Author David Maiolo .NOTES #>
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
        $Querys
    )

    $validQuerys = @()

    foreach ($Query in $Querys){
        if (Get-MicrosoftGraphRESTRequest -uri $Query.Query){
            Write-Host $Query.DisplayName"is valid." -ForegroundColor Green
            $validQuerys+=$Query
        }else{
            Write-Host $Query.DisplayName"is invalid." -ForegroundColor Red
        }
    }
}

#Reporting Functions
function Get-IntuneTenantReport{
<# .SYNOPSIS This function creates the HTML Report of Tenant Settings. .Author David Maiolo .NOTES #>
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
    $IntuneManagementQueryCollections
    )

    #process the report application Menu
    clear
    Write-Host "========================================="
    Write-Host "Intune Tenant HTML Report Wizard"
    Write-Host "========================================="
    Write-Host "[1] Create HTML Report Intune Tenant Settings"
    Write-Host "[2] Instructions"
    Write-Host "[3] Return To Main Menu" -ForegroundColor Yellow

    do {
        try {$numOk = $true;[int]$ans = Read-host "Enter Selection"}
        catch {$numOK = $false}
        } # end do 
    until (($ans -ge 1 -and $ans -le 3) -and $numOK)

    switch ($ans)
    {
        '1' {  
                Write-Host "Please choose a folder to save the report..."

                #Setup A CSV File That Can Be Used for the Report
                $reportfolderroot = Get-Folder
                $reportValuesFile = "intune_tenant_report(" + $(get-date -f dd-MM-yyyy-H-mm-ss) + ").html"
                $reportValuesFilePath = Join-Path $reportfolderroot $reportValuesFile

                #Create the template for the HTML report
                $reportHTMLHead = '<!doctype html>

Intune Tenant Report

 

Created with ‘+$global:ApplicationName+’ ‘+$global:ApplicationVersion+'(by ‘+$global:ApplicationAuthor+’)’+’

'
                $reportHTMLFoot = ''

                #Create head of HTML Report
                $reportHTMLHead | Out-File -filepath $reportValuesFilePath -Append

                if(!(Test-Path $reportfolderroot)){Write-Host "You chose an improper folder or the folder could not be accessed. Cannot continue." -ForegroundColor Red;break}

                Write-Host "Starting Intune Tenant HTML Report Process to $reportfolderroot..."
                sleep 5

                foreach ($IntuneManagementQueryCollection in $IntuneManagementQueryCollections){

                    $DisplayName = $($IntuneManagementQueryCollection.DisplayName)
                    $Querys = Invoke-Expression $($IntuneManagementQueryCollection.Querys)
                    
                    #create the Main Category String
                    $Category = $($IntuneManagementQueryCollection.DisplayName)
                    Write-Host $Category -ForegroundColor Cyan

                    #Output Category Heading to HTML Report
                    $CategoryHTMLstring = "

“+$Category+”

” $CategoryHTMLstring | Out-File -filepath $reportValuesFilePath -Append foreach ($Query in $Querys){ #Create the JSON Objects by running a REST query to Microsft Graph $objects = Get-MicrosoftGraphRESTRequest -uri $Query.Query if ($objects -ne $null -and $objects -ne $false){ #create the sub category string and count the items within it $countOnItem = $Query.DisplayName+”: “+(($objects| Measure-Object).Count)+” settings configured.” Write-Host “>>>”$countOnItem #Output Category Heading to HTML Report $countOnItemHTMLstring = ”

“+$countOnItem+”

” $countOnItemHTMLstring | Out-File -filepath $reportValuesFilePath -Append #Start a bulleted list $startHTMLList = ‘

    • ‘ $startHTMLList | Out-File -filepath $reportValuesFilePath -Append foreach ($object in $objects){ #create the item string $item = $object.id+”(“+$object.displayName+”)” Write-Host “>>>>>>”$item #Output item as bullet to HTML Report $itemHTMLstring = ”

    • “+$item+”

” $itemHTMLstring | Out-File -filepath $reportValuesFilePath -Append #$valuesFileOutput += [PSCustomObject] @{‘Category’ = $($IntuneManagementQueryCollection.DisplayName);’uri’ = $Query.Query; ‘displayName’ = $object.displayName; ‘count’ = ($object| Measure-Object).Count }; } #End the bulleted list of items $endHTMLList = ‘

‘ $endHTMLList | Out-File -filepath $reportValuesFilePath -Append }else{ #create the sub category string and count the items within it $countOnItem = $Query.DisplayName+”: 0 settings configured.” Write-Host “>>>”$countOnItem #Output Category Heading to HTML Report $countOnItemHTMLstring = ”

“+$countOnItem+”

” $countOnItemHTMLstring | Out-File -filepath $reportValuesFilePath -Append #Create a bullet underneat showing we found no settings $itemHTMLString = ‘

  • You have not configured this setting in your intune tenant.

‘ $itemHTMLstring | Out-File -filepath $reportValuesFilePath -Append } } } #Export the array of values to the restore values CSV Write-Host “Creating report values file $reportValuesFilePath…” #Create head of HTML Report $reportHTMLFoot | Out-File -filepath $reportValuesFilePath -Append Write-Host “The report process is complete. Please view your report $reportValuesFile” break } ‘2’ {Write-Host “Choose a folder to place your Intune Tenant report. This feature will report” Write-Host “all of your applied Intune Tenant settings in HTML format.” Write-Host “” Write-Host “Returning to the main menu in 20 seconds…” sleep 20 Get-IntuneManagementToolMenu } ‘3’ {Get-IntuneManagementToolMenu} } } #Backup/Restore Functions – General function Export-IntuneTenant{ <# .SYNOPSIS This function provides the main menu that backs up the intune tenant to file. .Author David Maiolo .NOTES #> param( [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)] $IntuneManagementQueryCollections ) #process the backup menu clear Write-Host “=========================================” Write-Host “Backup Your Entire Intune Tenant ” Write-Host “=========================================” Write-Host “[1] Backup Your Entire Intune Tenant” Write-Host “[2] Instructions” Write-Host “[3] Return To Main Menu” -ForegroundColor Yellow do { try {$numOk = $true;[int]$ans = Read-host “Enter Selection”} catch {$numOK = $false} } # end do until (($ans -ge 1 -and $ans -le 3) -and $numOK) switch ($ans) { ‘1’ { Write-Host “Please choose a folder to start the backup process…” $backupfolderroot = Get-Folder if(!(Test-Path $backupfolderroot)){Write-Host “You chose an improper folder or the folder could not be accessed. Cannot continue.” -ForegroundColor Red;break} Write-Host “Starting Intune Tenant Backup Process to $backupfolderroot…” sleep 5 foreach ($IntuneManagementQueryCollection in $IntuneManagementQueryCollections){ #Set component backup folder based on the queries main display name (as seen in the programs main menu) $subfolder = ($IntuneManagementQueryCollection.DisplayName) #Clean Up The Sub Folder Name by removing spaces, etc. $pattern = ‘[^a-zA-Z]’ $subfolder = $subfolder -replace $pattern $backupfolder = Join-Path $backupfolderroot $subfolder #Setup A CSV Values File That Can Be Used for a Restore $backupValuesFile = $subfolder+”_tenantRestoreFile(” + $(get-date -f dd-MM-yyyy-H-mm-ss) + “).csv” $backupValuesFilePath = Join-Path $backupfolderroot $backupValuesFile #Create an array to hold the temp values that will go into the CSV Values file $valuesFileOutput = @() #Create The Backup Subfolder if it does not exist already if(!(Test-Path $backupfolder)){ try{ New-Item -ItemType Directory -Force -Path $backupfolder }catch{ Write-Host “Error: Could not create $backupfolder. You probably don’t have rights” -ForegroundColor Red } } $DisplayName = $($IntuneManagementQueryCollection.DisplayName) $Querys = Invoke-Expression $($IntuneManagementQueryCollection.Querys) Write-Host “Backing up $($IntuneManagementQueryCollection.Querys) ($($IntuneManagementQueryCollection.DisplayName))…” -ForegroundColor Cyan Write-Host “==============================================================” -ForegroundColor Cyan Write-Host “Sub Folder: $backupfolder” -ForegroundColor Cyan foreach ($Query in $Querys){ #Create the JSON Objects by running a REST query to Microsft Graph $objects = Get-MicrosoftGraphRESTRequest -uri $Query.Query if ($objects -ne $null -and $objects -ne $false){ #Add values to the temp array for CSV Values file foreach ($object in $objects){ $filename = $object.id+”_(” + $(get-date -f dd-MM-yyyy-H-mm-ss) + “).json” $valuesFileOutput += [PSCustomObject] @{‘Category’ = $subfolder; ‘backupDir’ = $backupfolder; ‘backupFile’ = $filename; ‘uri’ = $Query.Query; ‘displayName’ = $object.displayName; }; } #Export each JSON object to a file Export-ObjectAsJson -automateAnswers -objects $objects -backupfolder “$backupfolder” }else{ Write-Host “Skipping due to no existance in your tenant:”$Query.DisplayName -ForegroundColor Yellow } } #Export the array of values to the restore values CSV Write-Host “Creating restore values file $backupValuesFilePath…” $valuesFileOutput | Export-CSv -Path $backupValuesFilePath -NoTypeInformation } Write-Host “The backup process is complete.” break } ‘2’ {Write-Host “Choose a folder to place your Intune Tenant Backup. This feature will backup” Write-Host “all of your Intune Tenant settings in JSON format and will create special CSV” Write-Host “restore files so that they can be restored with this application. Note: As is” Write-Host “the nature with REST GET/POST operations, some of the JSON files may need to be” Write-Host “modified by removing extra properties before they can be restored succesfully.” Write-Host “” Write-Host “Returning to the main menu in 20 seconds…” sleep 20 Get-IntuneManagementToolMenu } ‘3’ {Get-IntuneManagementToolMenu} } } function Import-IntuneTenant{ <# .SYNOPSIS This function provides the main menu that restores the intune tenant from file. .Author David Maiolo .NOTES #> #process the Main application Menu clear Write-Host “=========================================” Write-Host “Restore Your Intune Tenant” Write-Host “=========================================” Write-Host “[1] Restore From a CSV Restore Tenant File Created By This Program” Write-Host “[2] Restore an Individual Setting (JSON File)” Write-Host “[3] Instructions” Write-Host “[4] Return To Main Menu” -ForegroundColor Yellow do { try {$numOk = $true;[int]$ans = Read-host “Enter Selection”} catch {$numOK = $false} } # end do until (($ans -ge 1 -and $ans -le 4) -and $numOK) switch ($ans) { ‘1’ { #Open the file select dialog box to select restore csv Write-Host “Select Your CSV Restore File…” $OpenChooser = New-Object System.Windows.Forms.OpenFileDialog $OpenChooser.initialDirectory = $initialDirectory $OpenChooser.filter = “CSV Restore files (*.csv)| *.csv” $OpenChooser.ShowDialog() | Out-Null #Test That We Selected an Existant File try{Test-Path -path ($OpenChooser.filename)}catch{Write-Host “Could Not Access Selected File. Must Quit” -ForegroundColor Red;Break} $valuesFileInputs = Import-Csv -LiteralPath $OpenChooser.filename Write-Host “Select the folder containing the JSON files for this restore (originally “($valuesFileInputs[0].backupDir)”)…” $restorefolder = Get-Folder -initialDirectory ($valuesFileInputs[0].backupDir) #iterate through each of the lines in the file foreach($valuesFileInput in $valuesFileInputs){ #Get line x values from the CSV Restore file to use in restore logic $Category = $valuesFileInput.Category $backupDir = $valuesFileInput.backupDir $backupFile = $valuesFileInput.backupFile $uri = $valuesFileInput.uri $displayName = $valuesFileInput.displayName #Import the JSON File as raw JSON data $JSONFilePath = Join-Path $restorefolder $backupFile $JSONObject = Import-ObjectAsJson -JSONFilePath $JSONFilePath -automateAnswers #Post the JSON data to Microsoft Graph if ($JSONObject -ne $null -and $JSONObject -ne $false){ New-MicrosoftGraphJSONPost -uri $uri -JSONObject $JSONObject }else{ Write-Host “A null or false JSON object was skipped.” -ForegroundColor Yellow } } Write-Host “Tenant Restore Complete.” break } ‘2’ { #Import the JSON File as raw JSON data try{$JSONObject = Import-ObjectAsJson}catch{Write-Host “Invalid File Selected.” -ForegroundColor Red;break} $uri = Read-Host “Enter the full URI this JSON will be restored to (ex. https://graph.microsoft.com/beta/deviceManagement/conditionalAccessSettings)” #Post the JSON data to Microsoft Graph New-MicrosoftGraphJSONPost -uri $uri -JSONObject $JSONObject } ‘3’ { Write-Host “Before Many of the JSON files can be restored, they may need to be modified by removing extra properties” Write-Host “before they will post properly. For example, a mobile application must have the following properties only ” Write-Host “to post properly:” $Browser = @” { “@odata.type”: “#microsoft.graph.androidStoreApp”, “displayName”: “Intune Managed Browser”, “description”: “Intune Managed Browser”, “publisher”: “Microsoft Corporation”, “isFeatured”: true, “appStoreUrl”: “https://play.google.com/store/apps/details?id=com.microsoft.intune.mam.managedbrowser&hl=en”, “minimumSupportedOperatingSystem”: { “@odata.type”: “#microsoft.graph.androidMinimumOperatingSystem”, “v4_0”: true } “@ $Browser Write-Host “” Write-Host “Returning to the main menu in 20 seconds…” sleep 20 Get-IntuneManagementToolMenu } ‘4’ {Get-IntuneManagementToolMenu} Default {} } } function Export-ObjectAsJson{ <# .SYNOPSIS This function exports a PowrShell Obkect to a JSON formatted file (used as part of the backup procedure). .Author David Maiolo .NOTES #> param( [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)] $objects, [Parameter(Mandatory=$false,ValueFromPipeline=$true,Position=1)] [string]$backupfolder, [Parameter(Mandatory=$false,ValueFromPipeline=$true,Position=2)] [switch]$automateAnswers ) if($automateAnswers -and $backupfolder){ foreach ($object in $objects){ $filename = $object.id+”_(” + $(get-date -f dd-MM-yyyy-H-mm-ss) + “).json” #$filename = $object.id+ “.json” $backupfilepath = Join-Path “$backupfolder” “$filename” Write-Host “Backing Up $filename…” $JsonObject = $object | ConvertTo-Json $JsonObject | Out-File $backupfilepath } } else{ foreach ($object in $objects){ Write-Host “Manually choosing path…” $JsonObject = $object | ConvertTo-Json $SaveChooser = New-Object -TypeName System.Windows.Forms.SaveFileDialog $SaveChooser.filter = “JSON files (*.json)| *.json” $SaveChooser.filename = $object.DisplayName+”_” + $(get-date -f dd-MM-yyyy-H-mm-ss) + “.json” $SaveChooser.ShowDialog() $JsonObject | Out-File $SaveChooser.Filename } } } function Import-ObjectAsJson{ <# .SYNOPSIS This function imports a JSON formatted file as raw data (used as part of the restore procedure). .Author David Maiolo .NOTES #> param( [Parameter(Mandatory=$false,ValueFromPipeline=$true,Position=0)] [string]$JSONFilePath, [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=1)] [switch]$automateAnswers ) try{ if($JSONFilePath -and $automateAnswers){ $JSONData = Get-Content -Raw -Path $JSONFilePath }else{ $OpenChooser = New-Object System.Windows.Forms.OpenFileDialog $OpenChooser.initialDirectory = $initialDirectory $OpenChooser.filter = “JSON files (*.json)| *.json” $OpenChooser.ShowDialog() | Out-Null $JSONData = Get-Content -Raw -Path $OpenChooser.filename } }catch{ Throw “Could Not Import Object as JSON” } return $JSONData } function New-MicrosoftGraphJSONPost{ <# .SYNOPSIS This function is used to post to your Microsoft Graph request of choice. .Author David Maiolo .NOTES NAME: New-MicrosoftGraphJSONPost #> param( [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)] [string]$uri, [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)] [ValidateScript({ConvertFrom-Json $_ -ErrorAction Stop})] $JSONObject ) try{ Write-Host “Posting “($JSONObject | ConvertFrom-Json).id” to $uri…” Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSONObject -ContentType “application/json” Write-Host “Succesfully Posted JSON” -ForegroundColor Green }catch{ <#$ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); Write-Host “Response content:`n$responseBody” -ForegroundColor Red Write-Error “Post to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)” write-host #> Write-Host “Could not post JSON. You may need to modify the JSON to make it postbale.” -ForegroundColor Red return $false } } Function Get-Folder{ <# .SYNOPSIS This function will show the select folder GUI dialog box to the user. .Author David Maiolo .NOTES #> param( [string]$initialDirectory ) [System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) | Out-Null $foldername = New-Object System.Windows.Forms.FolderBrowserDialog if ($initialDirectory){ $foldername.SelectedPath = $initialDirectory }else{ $foldername.rootfolder = “UserProfile” } $result = $foldername.ShowDialog((New-Object System.Windows.Forms.Form -Property @{TopMost = $true })) if ($result -eq [Windows.Forms.DialogResult]::OK){ $folder += $foldername.SelectedPath } else { return $false } return $folder } #Menu Functions function Get-MainMenu { <# .SYNOPSIS This function is used to provide a main compment menu for the application. .Author David Maiolo .NOTES NAME: Get-IntuneMainMenu #> Param ( [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)] $Querys, [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)] [String]$QueryName, [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)] [Switch]$UseSubMenu ) #if (!($global:intuneWarehouseAuthResult) -or !($global:authToken)) { # throw “No authentication context. Authenticate first!” #} try { do { clear Write-Host “=========================================” Write-Host “$QueryName ” Write-Host “=========================================” $menu = @{} $i=0 foreach ($Query in $Querys) { $i++ Write-Host “[$i] $($Query.DisplayName)” $menu.Add($i,($Query.DisplayName)) if($Query -eq $Querys[-1]){ $iplusone = ($i+1) Write-Host “[$iplusone] Return To Main Menu” -ForegroundColor Yellow $menu.Add($iplusone,(“Exit”)) } } #Validate selection input is proper number range do { try {$numOk = $true;[int]$ans = Read-host “Enter Selection”} catch {$numOK = $false} } # end do until (($ans -ge 1 -and $ans -le $iplusone) -and $numOK) $selection = $menu.Item($ans); if ($selection -eq “Exit”){ Write-Host “Returning to Main Menu…” sleep -Milliseconds 300 Get-IntuneManagementToolMenu }else{ Write-Host “You Chose To Continue” Get-SubMenu -Query $Querys[$ans-1].Query -QueryName $Querys[$ans-1].DisplayName sleep -Milliseconds 300 } }while($ans -ne ($Querys.count+2)) }catch{ Write-Error “There was a problem rendering the menu.” break } } function Get-SubMenu { <# .SYNOPSIS This function is used to provide a sub menu for the application. This was written generically and can be used for other applications. .Author David Maiolo .NOTES NAME: Get-SubMenu #> Param ( [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)] $Query, [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)] [String]$QueryName ) try { do{ clear Write-Host “$QueryName ” Write-Host “=========================================” $QueryResults = Get-MicrosoftGraphRESTRequest -uri $Query $menu = @{} if($QueryResults -eq $null -or $QueryResults -eq $false){ Write-Host “There Are No $QueryName Configured In Your Intune Tenant!” -ForegroundColor Yellow Write-Host “[1] Go Back” $menu.Add(1,(“GoBack”)) }else{ $i=0 foreach ($QueryResult in $QueryResults) { $i++ if($QueryResult.displayName){ $DisplayName = ($QueryResult.displayName) }elseif($QueryResult.id){ $DisplayName = ($QueryResult.id) }else{ $DisplayName = ‘Unknown’ } if($DisplayName -ne $null) {$DisplayName = $DisplayName.ToString()} if($QueryResult.description){ $Description = $QueryResult.description $Description = $Description -replace “`n”,”, ” -replace “`r”,”, ” $Description = ($Description.ToCharArray() | select -first 75) -join “” Write-Host ” [$i] $($DisplayName) ($Description)” }else{ Write-Host ” [$i] $($DisplayName)” } $menu.Add($i,($QueryResult)) if($QueryResult -eq $QueryResults[-1]){ $iplusone = ($i+1) Write-Host ” [$iplusone] Go Back” -ForegroundColor Yellow $menu.Add($iplusone,(“GoBack”)) } } } #Validate selection input is proper number range do { try {$numOk = $true;[int]$ans = Read-host “Enter Selection”} catch {$numOK = $false} } # end do until (($ans -ge 1 -and $ans -le $iplusone) -and $numOK) $selection = $menu.Item($ans); if ($selection -eq “GoBack”){ Write-Host “Returning…” sleep -Milliseconds 300 return }else{ Write-Host “Rending in Out-Gridview…” Sleep -Milliseconds 300 $selection | Out-GridView } }while($selection -ne “GoBack”) }catch{ Write-Error “There was a problem rendering the menu.” break } } #Intune Queries function Get-MicrosoftGraphIntuneDeviceEnrollmentQueries{ <# .SYNOPSIS This function contains all of the URI requests for Device Enrollment. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneDeviceEnrollmentQueries = @() $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/termsAndConditions’; ‘DisplayName’ = ‘Terms and Conditions’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/androidForWorkSettings’; ‘DisplayName’ = ‘Android For Work Settings’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/androidForWorkAppConfigurationSchemas’; ‘DisplayName’ = ‘Android For Work App Configuration Schemas’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/androidForWorkEnrollmentProfiles’; ‘DisplayName’ = ‘Android For Work Enrollment Profiles’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/enrollmentProfiles’; ‘DisplayName’ = ‘Enrollment Profiles’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/importedDeviceIdentities’; ‘DisplayName’ = ‘Imported Device Identities’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/importedAppleDeviceIdentities’; ‘DisplayName’ = ‘Imported Apple Device Identities’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/remoteActionAudits’; ‘DisplayName’ = ‘Remote Action Audits’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/applePushNotificationCertificate’; ‘DisplayName’ = ‘Apple Push Notification Certificate’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceCategories’; ‘DisplayName’ = ‘Device Categories’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotSettings’; ‘DisplayName’ = ‘Windows Autopilot Settings’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities’; ‘DisplayName’ = ‘Windows Autopilot Device Identities’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles’; ‘DisplayName’ = ‘Windows Autopilot Deployment Profiles’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations’; ‘DisplayName’ = ‘Device Enrollment Configurations’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings’; ‘DisplayName’ = ‘DEP Onboarding Settings’}; $MicrosoftGraphIntuneDeviceEnrollmentQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/ndesConnectors’; ‘DisplayName’ = ‘NDES Connectors’}; return $MicrosoftGraphIntuneDeviceEnrollmentQueries } function Get-MicrosoftGraphIntuneDeviceComplianceQueries{ <# .SYNOPSIS This function contains all of the URI requests for Device Compliance. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneDeviceComplianceQueries = @() $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies’; ‘DisplayName’ = ‘Device Compliance Policies’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicyDeviceStateSummary’; ‘DisplayName’ = ‘Device Compliance Policy Device State Summary’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicySettingStateSummaries’; ‘DisplayName’ = ‘Device Compliance Policy Setting State Summaries’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/auditEvents’; ‘DisplayName’ = ‘Audit Events’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors’; ‘DisplayName’ = ‘Mobile Threat Defense Connectors’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceManagementPartners’; ‘DisplayName’ = ‘Device Management Partners’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/remoteAssistancePartners’; ‘DisplayName’ = ‘Remote Assistance Partners’}; $MicrosoftGraphIntuneDeviceComplianceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/notificationMessageTemplates’; ‘DisplayName’ = ‘Notification Message Templates’}; return $MicrosoftGraphIntuneDeviceComplianceQueries } function Get-MicrosoftGraphIntuneDeviceConfigurationQueries{ <# .SYNOPSIS This function contains all of the URI requests for Device Configurations. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneDeviceConfigurationQueries = @() $MicrosoftGraphIntuneDeviceConfigurationQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations’; ‘DisplayName’ = ‘Device Configurations’}; $MicrosoftGraphIntuneDeviceConfigurationQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts’; ‘DisplayName’ = ‘Device Management Scripts’}; $MicrosoftGraphIntuneDeviceConfigurationQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/telecomExpenseManagementPartners’; ‘DisplayName’ = ‘Telecom Expense Management Partners’}; $MicrosoftGraphIntuneDeviceConfigurationQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceSetupConfigurations’; ‘DisplayName’ = ‘Device Setup Configurations’}; $MicrosoftGraphIntuneDeviceConfigurationQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/deviceConfigurationDeviceStateSummaries’; ‘DisplayName’ = ‘Device Configuration Device State Summaries’}; return $MicrosoftGraphIntuneDeviceConfigurationQueries } function Get-MicrosoftGraphIntuneDeviceQueries{ <# .SYNOPSIS This function contains all of the URI requests for Devices. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneDeviceQueries = @() $MicrosoftGraphIntuneDeviceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/managedDeviceOverview’; ‘DisplayName’ = ‘Managed Device Overview’}; $MicrosoftGraphIntuneDeviceQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/managedDevices’; ‘DisplayName’ = ‘Managed Devices’}; return $MicrosoftGraphIntuneDeviceQueries } function Get-MicrosoftGraphIntuneAppQueries{ <# .SYNOPSIS This function contains all of the URI requests for App Queries. It is currently not used. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneAppQueries = @() $MicrosoftGraphIntuneAppQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/detectedApps’; ‘DisplayName’ = ‘Detected Apps’}; $MicrosoftGraphIntuneAppQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsInformationProtectionAppLearningSummaries’; ‘DisplayName’ = ‘Windows Information Protection App Learning Summaries’}; return $MicrosoftGraphIntuneAppQueries } function Get-MicrosoftGraphIntuneEbookQueries{ <# .SYNOPSIS This function contains all of the URI requests for eBooks. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneEbookQueries = @() $MicrosoftGraphIntuneEbookQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/managedEBooks’; ‘DisplayName’ = ‘Managed eBooks’}; return $MicrosoftGraphIntuneEbookQueries } function Get-MicrosoftGraphIntuneConditionalAccessQueries{ <# .SYNOPSIS This function contains all of the URI requests for Conditional Access. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneConditionalAccessQueries = @() $MicrosoftGraphIntuneConditionalAccessQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/conditionalAccessSettings’; ‘DisplayName’ = ‘Conditional Access Settings’}; $MicrosoftGraphIntuneConditionalAccessQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/exchangeConnectors’; ‘DisplayName’ = ‘Exchange Connectors’}; return $MicrosoftGraphIntuneConditionalAccessQueries } function Get-MicrosoftGraphIntuneOnPremisesQueries{ <# .SYNOPSIS This function contains all of the URI requests for On Premises Access. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneOnPremisesQueries = @() $MicrosoftGraphIntuneOnPremisesQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/exchangeOnPremisesPolicy’; ‘DisplayName’ = ‘Exchange On Premises Policy’}; $MicrosoftGraphIntuneOnPremisesQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/exchangeOnPremisesPolicies’; ‘DisplayName’ = ‘Exchange On Premises Policies’}; return $MicrosoftGraphIntuneOnPremisesQueries } function Get-MicrosoftGraphIntuneRolesQueries{ <# .SYNOPSIS This function contains all of the URI requests for Intune Roles (RBAC). .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneRolesQueries = @() $MicrosoftGraphIntuneRolesQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/roleDefinitions’; ‘DisplayName’ = ‘Role Definitions’}; $MicrosoftGraphIntuneRolesQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/roleAssignments’; ‘DisplayName’ = ‘Role Assignments’}; return $MicrosoftGraphIntuneRolesQueries } function Get-MicrosoftGraphIntuneSoftwareUpdatesQueries{ <# .SYNOPSIS This function contains all of the URI requests for Software Updates. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneSoftwareUpdatesQueries = @() $MicrosoftGraphIntuneSoftwareUpdatesQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/softwareUpdateStatusSummary’; ‘DisplayName’ = ‘Software Update Status Summary’}; $MicrosoftGraphIntuneSoftwareUpdatesQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/iosUpdateStatuses’; ‘DisplayName’ = ‘iOS Update Statuses’}; return $MicrosoftGraphIntuneSoftwareUpdatesQueries } function Get-MicrosoftGraphIntuneTroubleshootingQueries{ <# .SYNOPSIS This function contains all of the URI requests for Troubleshooting. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneTroubleshootingQueries = @() $MicrosoftGraphIntuneTroubleshootingQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/troubleshootingEvents’; ‘DisplayName’ = ‘Troubleshooting Events’}; return $MicrosoftGraphIntuneTroubleshootingQueries } function Get-MicrosoftGraphIntuneMiscQueries{ <# .SYNOPSIS This function contains all of the URI requests for Misc Items. It is currently not being used. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneMiscQueries = @() $MicrosoftGraphIntuneMiscQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsMalwareInformation’; ‘DisplayName’ = ‘Windows Malware Information’}; $MicrosoftGraphIntuneMiscQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/cartToClassAssociations’; ‘DisplayName’ = ‘Cart To Class Associations’}; $MicrosoftGraphIntuneMiscQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/resourceOperations’; ‘DisplayName’ = ‘Resource Operations’}; $MicrosoftGraphIntuneMiscQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsInformationProtectionNetworkLearningSummaries’; ‘DisplayName’ = ‘Windows Information Protection Network Learning Summaries’}; return $MicrosoftGraphIntuneMiscQueries } function Get-MicrosoftGraphIntuneAppManagementQueries{ <# .SYNOPSIS This function contains all of the URI requests for MAM. .Author David Maiolo .NOTES #> $MicrosoftGraphIntuneAppManagementQueries = @() $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/mobileApps’; ‘DisplayName’ = ‘Mobile Apps’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/mobileAppCategories’; ‘DisplayName’ = ‘Mobile App Categories’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/windowsManagementApp’; ‘DisplayName’ = ‘Windows Management App’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/enterpriseCodeSigningCertificates’; ‘DisplayName’ = ‘Enterprise Code Signing Certificates’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/iosLobAppProvisioningConfigurations’; ‘DisplayName’ = ‘iOS Lob App Provisioning Configurations’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/symantecCodeSigningCertificate’; ‘DisplayName’ = ‘Symantec CodeSigning Certificate’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations’; ‘DisplayName’ = ‘Mobile App Configurations’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/sideLoadingKeys’; ‘DisplayName’ = ‘Side Loading Keys’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/vppTokens’; ‘DisplayName’ = ‘VPP Tokens’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies’; ‘DisplayName’ = ‘Managed App Policies’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/iosManagedAppProtections’; ‘DisplayName’ = ‘iOS Managed App Protections’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/androidManagedAppProtections’; ‘DisplayName’ = ‘Android Managed App Protections’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/defaultManagedAppProtections’; ‘DisplayName’ = ‘Default Managed App Protections’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/targetedManagedAppConfigurations’; ‘DisplayName’ = ‘Targeted Managed App Configurations’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/mdmWindowsInformationProtectionPolicies’; ‘DisplayName’ = ‘MDM Windows Information Protection Policies’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/windowsInformationProtectionPolicies’; ‘DisplayName’ = ‘Windows Information Protection Policies’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/managedAppRegistrations’; ‘DisplayName’ = ‘Managed App Registrations’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceAppManagement/managedAppStatuses’; ‘DisplayName’ = ‘Managed App Statuses’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/detectedApps’; ‘DisplayName’ = ‘Detected Apps’}; $MicrosoftGraphIntuneAppManagementQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/beta/deviceManagement/windowsInformationProtectionAppLearningSummaries’; ‘DisplayName’ = ‘Windows Information Protection App Learning Summaries’}; return $MicrosoftGraphIntuneAppManagementQueries } function Get-MicrosoftGraphAzureUsersQueries{ <# .SYNOPSIS This function contains all of the URI requests for Azure Users. .Author David Maiolo .NOTES #> $MicrosoftGraphAzureUsersQueries = @() $MicrosoftGraphAzureUsersQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/v1.0/users’; ‘DisplayName’ = ‘Azure AD Users’}; return $MicrosoftGraphAzureUsersQueries } function Get-MicrosoftGraphAzureGroupsQueries{ <# .SYNOPSIS This function contains all of the URI requests for Azure Groups. .Author David Maiolo .NOTES #> $MicrosoftGraphAzureGroupsQueries = @() $MicrosoftGraphAzureGroupsQueries += [PSCustomObject] @{‘Query’ = ‘https://graph.microsoft.com/v1.0/groups’; ‘DisplayName’ = ‘Azure AD Groups’}; return $MicrosoftGraphAzureGroupsQueries } #Authentication Functions function Get-AuthToken { <# .SYNOPSIS This function returns an Auth Token used for the REST request. .Author Microsoft with small modifications by David Maiolo .NOTES #> [cmdletbinding()] param ( [Parameter(Mandatory=$true)] $User ) $userUpn = New-Object “System.Net.Mail.MailAddress” -ArgumentList $User $tenant = $userUpn.Host Write-Host “Checking for AzureAD module…” $AadModule = Get-Module -Name “AzureAD” -ListAvailable if ($AadModule -eq $null) { Write-Host “AzureAD PowerShell module not found, looking for AzureADPreview” $AadModule = Get-Module -Name “AzureADPreview” -ListAvailable } if ($AadModule -eq $null) { write-host write-host “AzureAD Powershell module not installed…” -f Red write-host “Install by running ‘Install-Module AzureAD’ or ‘Install-Module AzureADPreview’ from an elevated PowerShell prompt” -f Yellow write-host “Script can’t continue…” -f Red write-host exit } # Getting path to ActiveDirectory Assemblies # If the module count is greater than 1 find the latest version if($AadModule.count -gt 1){ $Latest_Version = ($AadModule | select version | Sort-Object)[-1] $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version } # Checking if there are multiple versions of the same module found if($AadModule.count -gt 1){ $aadModule = $AadModule | select -Unique } $adal = Join-Path $AadModule.ModuleBase “Microsoft.IdentityModel.Clients.ActiveDirectory.dll” $adalforms = Join-Path $AadModule.ModuleBase “Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll” } else { $adal = Join-Path $AadModule.ModuleBase “Microsoft.IdentityModel.Clients.ActiveDirectory.dll” $adalforms = Join-Path $AadModule.ModuleBase “Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll” } [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null $clientId = “d1ddf0e4-d672-4dae-b554-9d5bdfd93547” $redirectUri = “urn:ietf:wg:oauth:2.0:oob” $resourceAppIdURI = “https://graph.microsoft.com” $authority = “https://login.microsoftonline.com/$Tenant” try { $authContext = New-Object “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext” -ArgumentList $authority # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession $platformParameters = New-Object “Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters” -ArgumentList “Auto” $userId = New-Object “Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier” -ArgumentList ($User, “OptionalDisplayableId”) $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result # If the accesstoken is valid then create the authentication header if($authResult.AccessToken){ # Creating header for Authorization token $authHeader = @{ ‘Content-Type’=’application/json’ ‘Authorization’=”Bearer ” + $authResult.AccessToken ‘ExpiresOn’=$authResult.ExpiresOn } return $authHeader } else { Write-Host Write-Host “Authorization Access Token is null, please re-run application and asure you are conneted to the internet.” -ForegroundColor Red Write-Host break } } catch { write-host $_.Exception.Message -f Red write-host $_.Exception.ItemName -f Red write-host break } }

Overview

I created this application, Microsoft Graph Query Development Tool, to allow you to connect to your Microsoft Graph API to run REST requests. This is a development tool and allows you to work with Microsoft Graph data as an array that is returned when running the tool. You can also simply run the tool and view the data, but the intended purpose is to pipe the data into another application.

Microsoft Graph basics and how they apply to this tool

Microsoft Graph is the API that sits directly below your Intune Console and is the method to interact with live Intune data (or other online Microsoft services, such as Outlook, Office 365 and Azure). When you use your Intune console, the console is actually making a REST API request/response pair with Microsoft Graph where a GET method is sent with a request URI and a MIME-encoded response object is returned in the HTTP response body back to the Intune console.

For example, if you open your Microsoft Edge Browser and connect to your Azure Portal https://portal.azure.com/ you can view the Microsoft Graph request URI that Intune makes to retrieve the data displayed in the console.

For example open Intune -> Devices -> Compliant Devices. Now, open the Developer Options by hitting F12. Click the Network Column and search for requests to https://graph.microsoft.com. On the right hand side, under Headers, you will see you exact request URI:

In this example, the request URI was to retrieve my compliant devices:

https://graph.microsoft.com/beta/deviceManagement/managedDevices?$filter=(((managementAgent eq ‘mdm’) or (managementAgent eq ‘easMdm’)))&$top=25&_=1518599459508

Deconstructing the request URI, you can begin to understand the components that will be used with my development tool to make the same request:

API Version: Beta
Resource: deviceManagement/managedDevices
Query: (((managementAgent eq ‘mdm’) or (managementAgent eq ‘easMdm’)))&$top=25&_=1518599459508
Query Type: filter

Note these values and how they were derived from the request URI, as they will be used as the inputs for the tool. This is a request URI that returns compliant devices, and running the request within the tool will return the same JSON content to you, but in a form you can work on within an application by converting it to a PowerShell object.

Getting Your Environment Ready for Microsoft Graph Query Development Tool

This application was written in PowerShell and needs to be connected to the internet in order to run. It also requires the AzureAD module for ADAL (Azure Active Directory Authentication Libraries) authentication. It will attempt to download this module automatically if you do not already have it installed. However, feel free to get your environment ready by running:


  Install-Module AzureAD

Installing the Microsoft Graph Query Development Tool

Download the required files below and unzip the files into a directory of your choosing.


Once downloaded, modify the main application file, Invoke-MicrosoftGraphRequestTool.ps1, and change this $user single value at the bottom of the script to match the administrative account for either Intune, AzureAD or whatever cloud application you want to make the Microsoft Graph request URI against.


  $user = youradminaccount@company.onmicrosoft.com

Running the Maiolo Microsoft Graph Query Development Tool

To run the application, run the Invoke-MicrosoftGraphRequestTool.ps1 file within PowerShell and you will immediately be prompted to log into your Azure tenant. Although this login method may seem similar to some of my other tools, the underlying authentication method is different when connecting to the Intune Data Warehouse vs running a Microsoft Graph Request, so any authentication tokens you have open with my other tools will be insufficient here without logging in again.

Once logged in, you will be presented with the application’s main menu:

Run Custom Graph Request

This is the most basic version of a request URI and does not support a query. It only asks for the API version, which is typically “Beta” or “v1.0” and the Resource, such as “deviceAppManagement/mobileApps”, which will return all of the Mobile Applications in your Intune environment.

Run Custom Graph Request (GUI Assisted)

This is the most advanced portion of the application and will allow you to input a full Microsoft Graph request URI, including an optional query. This will bring up a small GUI window where you can build your request URI. Remember the request URI we found from our Intune console on the F12 development box earlier? Let’s use that URI to build our request URI within the application so we can return the same results.

A request URI is then built and invoked with the proper GET request.

This data is then returned, where we can either pass it on to the pipeline or just simply view it:

For example, we could make a small modification to Invoke-MicrosoftGraphRequestTool.ps1 and pass the data to Out-Gridview:


  Invoke-MicrosoftGraphRequestTool -user $user | Out-GridView

Now, when we re-run the application, the data is piped and viewed in Out-GrideView.

Replace Out-GridView with whatever application you would want to next see the data. That’s how the tool works!

The Invocation Function: Invoke-MicrosoftGraphRequestTool


<#
.Synopsis
    Connect to Microsoft Graph to GET a Microsoft Graph request URI JSON content return. This is a development tool to return Graph API content which can be fed to the pipleline to be ingested by another application.
.Author
    David Maiolo
.Help
    $user variable is your InTune tenant admin account such as admin@yourcompany.onmicrosoft.com
        If you are super new to all of this and dont even know what this is, create one by searching "Intune trial" and go through the process
#>

function Invoke-MicrosoftGraphRequestTool{

    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   [ValidateScript({$_ -like '*onmicrosoft.com*'})]
                   [string]$user
    )

    #Welcome Screen
    clear
    Write-Host "==============================================="-ForegroundColor Cyan
    Write-Host "Maiolo Microsoft Graph Query Development Tool"-ForegroundColor Cyan
    Write-Host "==============================================="-ForegroundColor Cyan
    Write-Host "v1.0 (2018-02-13) by dmaiolo"-ForegroundColor Cyan
    Write-Host "Connect to Microsoft Graph to Run Graph GET Requests. This is a "
    Write-Host "development tool to return graph data which can be fed to the "
    Write-Host "pipleline to be ingested by another application."
    Write-Host "========================================="-ForegroundColor Cyan

    #Import Local Modules
    $ModulePath = "$PSScriptRoot\MicrosoftGraphRequestToolModules.psm1"
    $localmodules = @("$ModulePath")
    foreach ($localmodule in $localmodules){
        if (Test-Path "$ModulePath") {
            Write-Host "Confirmed: Module Path $ModulePath exists."
            try{
                Write-Host "Importing $localmodule..."
                Import-Module $localmodule -Force
                }
            catch{
                throw "Error: Could not import $localmodule."
            }
        }else {
            throw "Error: Module Path '$ModulePath' doesn't exist."
        }
    }
    
    $modules = @("AzureAD")
    #Install Azure AD modules 
    foreach ($module in $modules){
        if (Get-Module -ListAvailable -Name $module) {
            Write-Host "Confirmed: $module module was already imported."
        } else {
            try{
                Write-Host "$module module does not exist. Installing $module..." -ForegroundColor Yellow
                Install-Module $module
            }catch{
                throw "Could not install required module $module. Tried to run `"Install-Module $module`" but it didn't work."

            }
        }
    }

    # Checking if authToken exists before running authentication
    if($global:authToken){
        #Get Universal Date and Time
        $DateTime = (Get-Date).ToUniversalTime()
        # If the authToken exists checking when it expires
        $TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes

        if($TokenExpires -le 0){
            write-host "Warning: Authentication Token expired" $TokenExpires "minutes ago." -ForegroundColor Yellow
            $global:authToken = Get-AuthToken -User $User
        }else{
            Write-Host "User $user has already been authenticated. (against Microsoft Graph)"
        }
    }else{
        $global:authToken = Get-AuthToken -User $User
    }
    sleep 5

    #Menu
    $Collections = Get-MicrosoftGraphMainCategories
    $results = Get-IntuneMainMenu -Collections $Collections -CollectionName "Maiolo Microsoft Graph Query Development Tool"
    Write-Host "===================================="
    Write-Host "Summary of Graph Query Findings"
    Write-Host "===================================="
    Write-Host "Number of values returned:"($results.count)
    $i=0
    #Write-Host "Summary of First 5 Findings..."
    #$results | select DisplayName,id -First 5| FT -AutoSize
    #Write-Host "Returning full results..."
    #sleep 5

    return  $results

}

$user = "user@company.onmicrosoft.com"

Invoke-MicrosoftGraphRequestTool -user $user

The Methods: MicrosoftGraphRequestToolModules.psm1


#Functions Specific TO Microsoft Graph

function Get-AuthToken {

<#
.SYNOPSIS
This function is used to authenticate with the Graph API REST interface
.DESCRIPTION
The function authenticate with the Graph API Interface with the tenant name
.EXAMPLE
Get-AuthToken
Authenticates you with the Graph API interface
.NOTES
NAME: Get-AuthToken
#>

[cmdletbinding()]

param
(
    [Parameter(Mandatory=$true)]
    $User
)

$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User

$tenant = $userUpn.Host

Write-Host "Checking for AzureAD module..."

    $AadModule = Get-Module -Name "AzureAD" -ListAvailable

    if ($AadModule -eq $null) {

        Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable

    }

    if ($AadModule -eq $null) {
        write-host
        write-host "AzureAD Powershell module not installed..." -f Red
        write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
        write-host "Script can't continue..." -f Red
        write-host
        exit
    }

# Getting path to ActiveDirectory Assemblies
# If the module count is greater than 1 find the latest version

    if($AadModule.count -gt 1){

        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]

        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }

            # Checking if there are multiple versions of the same module found

            if($AadModule.count -gt 1){

            $aadModule = $AadModule | select -Unique

            }

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

    else {

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null

[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"

$redirectUri = "urn:ietf:wg:oauth:2.0:oob"

$resourceAppIdURI = "https://graph.microsoft.com"

$authority = "https://login.microsoftonline.com/$Tenant"

    try {

    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

    # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
    # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession

    $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"

    $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")

    $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result

        # If the accesstoken is valid then create the authentication header

        if($authResult.AccessToken){

        # Creating header for Authorization token

        $authHeader = @{
            'Content-Type'='application/json'
            'Authorization'="Bearer " + $authResult.AccessToken
            'ExpiresOn'=$authResult.ExpiresOn
            }

        return $authHeader

        }

        else {

        Write-Host
        Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red
        Write-Host
        break

        }

    }

    catch {

    write-host $_.Exception.Message -f Red
    write-host $_.Exception.ItemName -f Red
    write-host
    break

    }

}

function Get-MicrosoftGraphMainCategories{
<#
.SYNOPSIS
    This function is used to provide the Main applicaiton menu what to display and Data warehouse collections to call when invoked.
.Author
    David Maiolo
.NOTES
    NAME: Get-MicrosoftGraphMainCategories
#>

    $GraphCategories = @()
    $GraphCategories += [PSCustomObject] @{'name' = 'Get-MicrosoftGraphRESTRequest'; 'DisplayName' = 'Run Custom Graph Request'};
    $GraphCategories += [PSCustomObject] @{'name' = 'Show-Command Get-MicrosoftGraphRESTRequest'; 'DisplayName' = 'Run Custom Graph Request (GUI Assisted)'};
    $GraphCategories += [PSCustomObject] @{'name' = 'Get-MicrosoftGraphRESTRequest -graphApiVersion "Beta" -Resource "deviceAppManagement/mobileApps"'; 'DisplayName' = 'Run Sample Graph Request: Mobile Apps'};
    
    return $GraphCategories
}

function Get-MicrosoftGraphRESTRequest{
<#
.SYNOPSIS
    This function is used to call your Microsoft Graph request of choice and return the results as an array.
.Author
    David Maiolo
.NOTES
    NAME: Get-MicrosoftGraphRESTRequest
#>
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
        [ValidateSet ("Beta","v1.0")]
        [string]$graphApiVersion,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
        [String]$Resource,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)]
        [ValidateSet ("count","expand","filter","format","orderby","search","select","skip","skipToken","top")]
        [String]$QueryType,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=3)]
        [String]$Query

    )

    try{
        if(([bool]($MyInvocation.BoundParameters.Keys -contains 'Query')) -and ([bool]($MyInvocation.BoundParameters.Keys -contains 'QueryType'))){
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)?`$$QueryType=$Query"
        }else{
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)"
        }
        Write-Host "Requesting $uri..."

        $GraphRequestValue = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value | ? { ($_.'@odata.type').Contains("managed") }

        return $GraphRequestValue

    }catch{
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        break
        
    }

}

#Universal Functions

function Convert-VariableNameToFriendlyName{
<#
.SYNOPSIS
    This function is used to convert a variable name, such as helloMyNameIsDavidMaiolo to a friendly variable name, such as Hello My Name Is David Maiolo.
.Author
    David Maiolo
.NOTES
    NAME: Convert-VariableNameToFriendlyName
#>
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
        [String]$VariableName
    )

    $FriendlyVariableName = ($VariableName -split '_')[0] -creplace '(?<=\w)([A-Z])', ' $1'
    $TextInfo = (Get-Culture).TextInfo

    $FriendlyVariableName = $TextInfo.ToTitleCase($FriendlyVariableName)

    return $FriendlyVariableName
}

function Get-IntuneMainMenu {
<#
.SYNOPSIS
    This function is used to provide a main menu for the application. This was written generically and can be used for other applications.
.Author
    David Maiolo
.NOTES
    NAME: Get-IntuneMainMenu
#>
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   $Collections,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
                   [String]$CollectionName,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)]
                   [Switch]$UseSubMenu
    )

    #if (!($global:intuneWarehouseAuthResult) -or !($global:authToken)) {
    #    throw "No authentication context. Authenticate first!"
    #}
    try {
        do {
            clear
            Write-Host "========================================="
            Write-Host $CollectionName
            Write-Host "========================================="
            #$menu = @{}
            $MenuArray = @()
        
            for ($i=1;$i -le $collections.count; $i++) { 
                Write-Host "[$i] $($collections[$i-1].DisplayName)"
                if($i -eq $Collections.count){
                    $iplusone = ($i+1)
                    Write-Host "[$iplusone] Exit"
                }
            }
            [int]$ans = Read-Host 'Enter Selection'

            if ($ans -eq ($collections.count+1)){
                break
            }else{
                $collection = Invoke-Expression $collections[$ans-1].Name
                #Write-Host Colection: $collection
                if(([bool]($MyInvocation.BoundParameters.Keys -contains 'UseSubMenu'))){
                    Get-IntuneSubMenu -Collections $collection -CollectionName $collections[$ans-1].DisplayName
                }else{
                    return $collection
                }
            }
        }while($ans -ne ($collections.count+1))
    }catch{
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        write-host
        sleep 10
        break
    }
}

function Get-IntuneSubMenu {
<#
.SYNOPSIS
    This function is used to provide a sub menu for the application. This was written generically and can be used for other applications.
.Author
    David Maiolo
.NOTES
    NAME: Get-IntuneSubMenu
#>
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   $Collections,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
                   [String]$CollectionName
    )

    #$collections = Get-IntuneDatawarehouseCollections

    if (!$global:intuneWarehouseAuthResult) {
        throw "No authentication context. Authenticate first by running 'Connect-IntuneDataWarehouse'"
    }

    try {
        do{
            clear
            Write-Host ">>>>>$CollectionName                          " -ForegroundColor Cyan
            Write-Host ">>>>>=========================================" -ForegroundColor Cyan
            $menu = @{}
            for ($i=1;$i -le $collections.count; $i++) { 
                Write-Host "     [$i] $($collections[$i-1].DisplayName)" 
                $menu.Add($i,($collections[$i-1].name))
                if($i -eq $Collections.count){
                    $iplusone = ($i+1)
                    Write-Host "     [$iplusone] Go Back"
                    $menu.Add($i+1,("GoBack"))
                }
            }
        
            [int]$ans = Read-Host 'Enter Selection'
            $selection = $menu.Item($ans);
        
            if ($selection -eq "GoBack"){
                #Menu
                #$Collections = Get-IntuneDatawarehouseMenuCategories
                #Get-IntuneMainMenu -Collections $Collections -CollectionName "InTune Management"
                return
            }else{
                Get-IntuneDataWarehouseCollection $selection | Out-GridView -Title (Convert-VariableNameToFriendlyName -VariableName $selection) 
                #Read-Host -Prompt "Press Enter to continue" 
            }
        }while($selection -ne "GoBack")

    }catch{
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        sleep 10
        break
    }
}


Overview

I created this application, Intune Warehouse Connector Application, to allow you to connect to your Intune Data Warehouse to query and read collection data that sits in the warehouse. Your Intune Data Warehouse differs from the data available to Intune as the Warehouse contains a historical collection of your Intune data. The Data Warehouse samples data daily to provide a historical view of your environment and devices, while also using data solutions like meadewillis that offer a complete system for warehouses and more. The view is composed of related things in time.

Intune Data Warehouse vs Microsoft Graph

Microsoft Graph is the API that sits directly below your Intune console and is the RESTful method to interact with live Intune data. The Intune Data Warehouse, conversely, is a look at historical data and connecting to the data warehouse uses different syntax and connection parameters than does interfacing with the Microsoft Graph API. In the same manner that PowerBI is used to connect to the Intune Data Warehouse to view historical data, this tool is used.

Getting Your Intune Data Warehouse API Ready for the Application

You will need three key pieces of data to use this application to connect to the Intune Data Warehouse:

  1. Your Intune Tenant Admin account, such as admin@yourcompany.onmicrosoft.com. This will be used in the application as the $user variable. If you are new to all of this and don’t even know what Intune is, create one by searching “Intune trial” and go through the process of setting up an Intune tenant.
  2. Your Data Warehouse URL, such as https://fef.msua02.manage.microsoft.com/ReportingService/DataWarehouseFEService?api-version=beta. This will be used in the application as the $WarehouseURL variable.
    1. This is found by logging into https://portal.azure.com -> Intune -> Set up Intune Data Warehouse -> Use third-party reporting services. You will find you data warehouse URL as plain text
  3. Your Intune Data Warehouse API application key, such as 2ffb63d3-9dc5-4427-85b6-6c4ce6bd7717. You will need to set this up, and will be used as the $ApplicationId variable in the application.
    1. Log into https://portal.azure.com -> Azure Active Directory -> Manage -> App registrations
    2. Click +New application registration
    3. Under the Create blade, specify the following:
      1. Name: Data Warehouse InTune API
      2. Type: Native
      3. Redirect URI: urn:ietf:wg:oauth:2.0:oob
    1. Click Create, then re-open this API you just created and go to Settings -> Required Permissions
    2. Click +Add -> Select an API
    3. Choose the Microsoft Intune API
    4. Check Get data warehouse information from Microsoft Intune and click Select
    5. Once again, re-open tis API and copy your Application ID. This is what we need!

Getting Your Environment Ready for the Intune Warehouse Connector Application

This application was written in PowerShell and needs to be connected to the internet in order to run. It also requires the AzureAD module for ADAL (Azure Active Directory Authentication Libraries) authentication. It will attempt to download this module automatically if you do not already have it installed. However, feel free to get your environment ready by running:


Install-Module AzureAD

Installing the Intune Warehouse Connector Application

Download the required files below and unzip them into a directory of your choosing.


Once downloaded, modify the main application file, Invoke-IntuneDatawarehouseConnector.ps1, and change these three values at the bottom of the script to match what we gathered earlier under the Getting Your Intune Data Warehouse API Ready for Intune Warehouse Connector Application section above.


$user = "intuneadmin@yourcompany.onmicrosoft.com"
$ApplicationId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee"
$WarehouseURL = "https://datawarehouseurl"

Running the Intune Warehouse Connector Application

To run the application, run the Invoke-IntuneDatawarehouseConnector.ps1 file within PowerShell and you will immediately be prompted to log into your Intune tenant:

Once logged in, you will be presented with the applications main menu:

Device and Device Policy Management

This section provides information on your devices and the device policies you have applied.

When launching a menu item, the results will display in a GUI window.

User Management

This section provides user management data.

MAM and MAM Policy Management

This section provides you with your Mobile Applications, their associated policies and other such MAM data.

Misc. Management

This section provides misc. management data about your Intune environment.

The Invocation Function: Invoke-IntuneDatawarehouseConnector.ps1


<# .Synopsis Intune Warehouse Connector Application. Connect to Microsoft Intune Data Warehouse .Version 0.8 .Author David Maiolo .Help $user variable is your InTune tenant admin account such as admin@yourcompany.onmicrosoft.com If you are super new to all of this and dont even know what this is, create one by searching "Intune trial" and go through the process $WarehouseURL is found at portal.azure.com -> Intune -> Set up Intune Data Warehouse -> Use third-party reporting services.
        for example, mine was "https://fef.msua02.manage.microsoft.com/ReportingService/DataWarehouseFEService?api-version=beta"
    $ApplicationId needs to be created. To do this, goto portal.azure.com -> Auze Active Directory -> Manage -> App registrations
        1. choose New application registration
        2. On the Create blade, specify the following:
            2a. Name: InTune API
            2b. Type: Native
            2c. Redirect URI: urn:ietf:wg:oauth:2.0:oob
        3. Save it, then re-open tis new Intune API app and goto Settings -> Required Permissions
            3a. Add -> Select API
            3b. Microsoft Intune API
        4. Save changes to Intune API app and use the ApplicationID as $ApplicationID
        5. When you first run this program it should ask you to authenticate with that $user's password
.Name
    Name: Invoke-IntuneDatawarehouseConnectorI
#>

function Invoke-IntuneDatawarehouseConnector{

    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   [ValidateScript({$_ -like '*onmicrosoft.com*'})]
                   [string]$user,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   [ValidateScript({$_ -like '*https://*'})]
                   [string]$WarehouseURL,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   [ValidateScript({$_ -match '([A-Za-z0-9\-]+)'})]
                   [string]$ApplicationId
    )

    #Welcome Screen
    clear
    Write-Host "=============================================="-ForegroundColor Cyan
    Write-Host "Maiolo Intune Warehouse Connector Application"-ForegroundColor Cyan
    Write-Host "=============================================="-ForegroundColor Cyan
    Write-Host "v1.0 (2018-02-13) by dmaiolo"-ForegroundColor Cyan
    Write-Host "=============================================="-ForegroundColor Cyan

    #Import Local Modules
    $ModulePath = "$PSScriptRoot\IntunePowerShellAPIModules.psm1"
    $localmodules = @("$ModulePath")
    foreach ($localmodule in $localmodules){
        if (Test-Path "$ModulePath") {
            Write-Host "Confirmed: Module Path $ModulePath exists."
            try{
                Write-Host "Importing $localmodule..."
                Import-Module $localmodule -Force
                }
            catch{
                throw "Error: Could not import $localmodule."
            }
        }else {
            throw "Error: Module Path '$ModulePath' doesn't exist."
        }
    }
    
    $modules = @("AzureAD")
    #Install Azure AD modules 
    foreach ($module in $modules){
        if (Get-Module -ListAvailable -Name $module) {
            Write-Host "Confirmed: $module module was already imported."
        } else {
            try{
                Write-Host "$module module does not exist. Installing $module..." -ForegroundColor Yellow
                Install-Module $module
            }catch{
                throw "Could not install required module $module. Tried to run `"Install-Module $module`" but it didn't work."

            }
        }
    }

    if (!(Test-IntuneDataWarehouseAuthentication)) {
        Write-Host "Authenticating $user..."
        Connect-IntuneDataWarehouse -User $User -ApplicationId $ApplicationId -DataWarehouseURL $WarehouseURL
    }else{
        Write-Host "User $user has already been authenticated. (with Microsoft Intune Datawarehouse)"
    }

    #Menu
    $Collections = Get-IntuneDatawarehouseMenuCategories
    Get-IntuneMainMenu -Collections $Collections -CollectionName "Welcome to InTune Management" -UseSubMenu
}

$user = "dmaiolo@stonystudio.onmicrosoft.com" #this is only an example. Use your own!
$ApplicationId = "0f27cbba-6ab5-42ea-8190-3fb7f5b56425" #this is only an example. Use your own!
$WarehouseURL = "https://fef.msua02.manage.microsoft.com/ReportingService/DataWarehouseFEService?api-version=beta" #this is only an example. Use your own!

Invoke-IntuneDatawarehouseConnector -user $user -ApplicationId $ApplicationId -WarehouseURL $WarehouseURL

The Methods: IntunePowerShellAPIModules.psm1


#Functions Specific To Microsoft Intune Data Warehouse

function Get-IntuneDatawarehouseMenuCategories{
<# .SYNOPSIS This function is used to provide the applicaiton menu what to display and functions to call when the menu item is pressed .Author David Maiolo .NOTES NAME: Get-IntuneDatawarehouseMenuCategories #>
    $DeviceCategories = @()
    $DeviceCategories += [PSCustomObject] @{'name' = 'Get-IntuneDataWarehouseDeviceCategories'; 'DisplayName' = 'Device and Device Policy Management'};
    $DeviceCategories += [PSCustomObject] @{'name' = 'Get-IntuneDataWarehouseUserCategories'; 'DisplayName' = 'User Management'};
    $DeviceCategories += [PSCustomObject] @{'name' = 'Get-IntuneDataWarehouseMAMCategories'; 'DisplayName' = 'MAM and MAM Policy Management'};
    $DeviceCategories += [PSCustomObject] @{'name' = 'Get-IntuneDataWarehouseMiscCategories'; 'DisplayName' = 'Misc Management'};

    return $DeviceCategories
}

function Get-IntuneDataWarehouseDeviceCategories {
<# .SYNOPSIS This function is used to provide the Device applicaiton menu what to display and Data warehouse collections to call when invoked. .Author David Maiolo .NOTES NAME: Get-IntuneDataWarehouseDeviceCategories #>

    $DeviceCollections = @()

    $DeviceCollections += [PSCustomObject] @{'name' = 'devices'; 'DisplayName' = 'Devices'};
    $DeviceCollections += [PSCustomObject] @{'name' = 'userDeviceAssociations'; 'DisplayName' = 'User Device Associations'};
    $DeviceCollections += [PSCustomObject] @{'name' = 'deviceTypes'; 'DisplayName' = 'Device Types'};
    $DeviceCollections += [PSCustomObject] @{'name' = 'devicesWithoutAssignment'; 'DisplayName' = 'Devices Without Assignments'};
    $DeviceCollections += [PSCustomObject] @{'name' = 'policies'; 'DisplayName' = 'Policies'};
    $DeviceCollections += [PSCustomObject] @{'name' = 'policyDeviceActivities'; 'DisplayName' = 'Policy Device Activities'};
    
    return $DeviceCollections
}

function Get-IntuneDataWarehouseUserCategories {
<# .SYNOPSIS This function is used to provide the User applicaiton menu what to display and Data warehouse collections to call when invoked. .Author David Maiolo .NOTES NAME: Get-IntuneDataWarehouseUserCategories #>

    $UserCollections = @()

    $UserCollections += [PSCustomObject] @{'name' = 'users'; 'DisplayName' = 'Users'};
    $UserCollections += [PSCustomObject] @{'name' = 'userDeviceAssociations'; 'DisplayName' = 'User Device Associations'};
    $UserCollections += [PSCustomObject] @{'name' = 'userTermsAndConditionsAcceptances'; 'DisplayName' = 'User Terms and Conditions Acceptances'};
    $UserCollections += [PSCustomObject] @{'name' = 'usersWithoutAssignment'; 'DisplayName' = 'Users Without Assignment'};
    $UserCollections += [PSCustomObject] @{'name' = 'currentUsers'; 'DisplayName' = 'Current Users'};
    $UserCollections += [PSCustomObject] @{'name' = 'ownerTypes'; 'DisplayName' = 'Owner Types'};
    
    return $UserCollections
}

function Get-IntuneDataWarehouseMAMCategories {
<# .SYNOPSIS This function is used to provide the User applicaiton menu what to display and Data warehouse collections to call when invoked. .Author David Maiolo .NOTES NAME: Get-IntuneDataWarehouseMAMCategories #>

    $UserCollections = @()

    $UserCollections += [PSCustomObject] @{'name' = 'mamApplications'; 'DisplayName' = 'Mam Applications'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamApplicationInstances'; 'DisplayName' = 'MAM Application Instances'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamPolicies'; 'DisplayName' = 'MAM Policies'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamPolicyTypes'; 'DisplayName' = 'MAM Policy Types'};
    $UserCollections += [PSCustomObject] @{'name' = 'mobileAppInstallStates'; 'DisplayName' = 'Mobile App Install States'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamPlatforms'; 'DisplayName' = 'MAM Platforms'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamEffectivePolicies'; 'DisplayName' = 'MAM Effective Policies'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamDeviceHealths'; 'DisplayName' = 'MAM Device Healths'};
    $UserCollections += [PSCustomObject] @{'name' = 'mamCheckins'; 'DisplayName' = 'MAM Checkins'};
    $UserCollections += [PSCustomObject] @{'name' = 'mobileAppDeviceUserInstallStatuses'; 'DisplayName' = 'Mobile App Device User Install Statuses'};
    $UserCollections += [PSCustomObject] @{'name' = 'appRevisions'; 'DisplayName' = 'App Revisions'};
    $UserCollections += [PSCustomObject] @{'name' = 'appTypes'; 'DisplayName' = 'App Types'};
    
    
    
    return $UserCollections
}

function Get-IntuneDataWarehouseMiscCategories {
<# .SYNOPSIS This function is used to provide the Misc applicaiton menu what to display and Data warehouse collections to call when invoked. .Author David Maiolo .NOTES NAME: Get-IntuneDataWarehouseMiscCategories #>

    $MiscCollections = @()

    $MiscCollections += [PSCustomObject] @{'name' = 'deviceConfigurationProfileUserActivities'; 'DisplayName' = 'Device Configuration Profile User Activities'};
    $MiscCollections += [PSCustomObject] @{'name' = 'deviceConfigurationProfileDeviceActivities'; 'DisplayName' = 'Device Configuration Profile Device Activities'};
    $MiscCollections += [PSCustomObject] @{'name' = 'policyPlatformTypes'; 'DisplayName' = 'Policy Platform Types'};
    $MiscCollections += [PSCustomObject] @{'name' = 'compliancePolicyStatusDeviceActivities'; 'DisplayName' = 'Compliance Policy Status Device Activities'};
    $MiscCollections += [PSCustomObject] @{'name' = 'compliancePolicyStatusDevicePerPolicyActivities'; 'DisplayName' = 'Compliance Policy Status Device Per Policy Activities'};
    $MiscCollections += [PSCustomObject] @{'name' = 'dates'; 'DisplayName' = 'Dates'};
    $MiscCollections += [PSCustomObject] @{'name' = 'intuneManagementExtensionHealthStates'; 'DisplayName' = 'Intune Management Extension Health States'};
    $MiscCollections += [PSCustomObject] @{'name' = 'intuneManagementExtensionVersions'; 'DisplayName' = 'Intune Management Extension Versions'};
    $MiscCollections += [PSCustomObject] @{'name' = 'intuneManagementExtensions'; 'DisplayName' = 'Intune Management Extensions'};
    $MiscCollections += [PSCustomObject] @{'name' = 'termsAndConditions'; 'DisplayName' = 'Terms and Conditions'};
    $MiscCollections += [PSCustomObject] @{'name' = 'enrollmentTypes'; 'DisplayName' = 'Enrollment Types'};
    $MiscCollections += [PSCustomObject] @{'name' = 'managementStates'; 'DisplayName' = 'Management States'};
    $MiscCollections += [PSCustomObject] @{'name' = 'enrollmentTypes'; 'DisplayName' = 'Enrollment Types'};
    $MiscCollections += [PSCustomObject] @{'name' = 'clientRegistrationStateTypes'; 'DisplayName' = 'Client Registration State Types'};
    $MiscCollections += [PSCustomObject] @{'name' = 'managementAgentTypes'; 'DisplayName' = 'Management Agent Types'};
    $MiscCollections += [PSCustomObject] @{'name' = 'mdmDeviceInventoryHistories'; 'DisplayName' = 'MDM Device Inventory Histories'};
    $MiscCollections += [PSCustomObject] @{'name' = 'workPlaceJoinStateTypes'; 'DisplayName' = 'Workplace Join State Types'};
    
    
    
    return $MiscCollections
}

function Get-IntuneDataWarehouseCollection {
<# .SYNOPSIS This function is used to get a collection of data from the Intune Data Warehouse .DESCRIPTION The function connects to the Data Warehouse URL and returns a collection of data .EXAMPLE Get-IntuneDataWarehouseCollection -CollectionName devices Returns all devices from the Data Warehouse .NOTES NAME: Get-IntuneDataWarehouseCollection #>
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        $CollectionName,
        $Skip=0,
        $Top=1000
    )

    if (!$global:intuneWarehouseAuthResult) {
        throw "No authentication context. Authenticate first by running 'Connect-IntuneDataWarehouse'"
    }

    try {
        # Verify that the request collection exists in the warehouse
        $validCollectionNames = Get-IntuneDataWarehouseCollectionNames

        if (!($validCollectionNames).contains("$CollectionName")) {
            throw "Collection Name $CollectionName doesn't exist."
        }
        else {
            $clientRequestId = [Guid]::NewGuid()
            $headers = @{
                            'Content-Type'='application/json'
                            'Authorization'="Bearer " + $global:intuneWarehouseAuthResult.AccessToken
                            'ExpiresOn'= $global:intuneWarehouseAuthResult.ExpiresOn
                            'client-request-id'=$clientRequestId
                        }
            $URL = $global:intuneWarehouseURL.Insert($global:intuneWarehouseURL.IndexOf("?"), "/$collectionName")
            $URL = "$URL&`$skip=$Skip&`$top=$Top"

            Write-Verbose "Request URL = $URL"
            Write-Verbose "Client request ID = $clientRequestId"
            $Response = Invoke-WebRequest -Uri $URL -Method Get -Headers $headers

            $responseJson = $Response.content | ConvertFrom-Json

            $returnvalues = $responseJson.value

            
            $friendlyreturnvalues = new-object psobject
            #return $returnvalues | Select @{Name=$_.;Expression={$_."Last Name"}
            $returnvalues | get-member -type NoteProperty | foreach-object {
              [string]$name=$_.Name ; 
              $value=$returnvalues."$($_.Name)"

              $FriendlyName = Convert-VariableNameToFriendlyName -VariableName $name
              $friendlyreturnvalues | Add-Member –membertype NoteProperty –name "$FriendlyName" –value "$value"
            }            

            return $returnvalues
        }
    }
    catch {
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -f Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        throw
    }
}

function Connect-IntuneDataWarehouse {

<# .SYNOPSIS This function is used to authenticate with the Azure Active Directory using ADAL Original Source: IntuneDataWareHouseCmdlet .Author Original Source: Microsoft with small modifications by David Maiolo .DESCRIPTION The function authenticates with Azure Active Directory with a UserPrincipalName .EXAMPLE Connect-IntuneDataWarehouse -ApplicationId ee6e1234-5655-4321-83f4-ef4fd36ce1c2 -User user@tenant.onmicrosoft.com Authenticates you to a specific Application ID within Azure Active Directory with the users UPN .NOTES NAME: Connect-IntuneDataWarehouse #>
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        $ApplicationId,
        [Parameter(Mandatory=$true)]
        $User,
        [Parameter(Mandatory=$true)]
        $DataWarehouseURL,
        $CredentialsFile,
        $RedirectUri='urn:ietf:wg:oauth:2.0:oob'
    )

    if (Test-IntuneDataWarehouseAuthentication -User $User) {
        Write-Host "User is already authenticated."
        return
    }

    $userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User
    $tenant = $userUpn.Host

    # Finding the AzureAD cmdlets that can be used for authentication.
    $AadModule = Get-Module -Name "AzureAD" -ListAvailable

    if ($AadModule -eq $null) {
        Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable
    }

    if ($AadModule -eq $null) {
        throw "AzureAD Powershell module not installed...Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt"
    }

    # Getting path to Active Directory Assemblies
    # If the module count is greater than 1 find the latest version
    if ($AadModule.count -gt 1) {

        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]
        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }

        # Checking if there are multiple versions of the same module found
        if ($AadModule.count -gt 1) {
            $aadModule = $AadModule | select -Unique
        }

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    }
    else {
        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    }

    [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
    [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

    $resourceAppIdURI = "https://api.manage.microsoft.com/"
    $authority = "https://login.windows.net/$Tenant"

    try {
        $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

        # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
        # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession
        $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
        $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")

        if ($CredentialsFile -eq $null){
            $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$ApplicationId,$RedirectUri,$platformParameters,$userId).Result
        }
        else {
            if (test-path "$CredentialsFile") {
                $UserPassword = Get-Content "$CredentialsFile" | ConvertTo-SecureString
                $userCredentials = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential -ArgumentList $userUPN,$UserPassword
                $authResult = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($authContext, $resourceAppIdURI, $ApplicationId, $userCredentials).Result;
            }
            else {
                throw "Path to Password file $Password doesn't exist, please specify a valid path..."
            }
        }

        if ($authResult.AccessToken) {
            $global:intuneWarehouseAuthResult = $authResult;
            $global:intuneWarehouseAuthUser = $User;
            $global:intuneWarehouseURL = $DataWarehouseURL;
        }
        else {
            throw "Authorization Access Token is null, please re-run authentication..."
        }
    }
    catch {
        write-host $_.Exception.Message -f Red
        write-host $_.Exception.ItemName -f Red
        write-host
        throw
    }
}

function Get-IntuneDataWarehouseCollectionNames {
<# .SYNOPSIS This function is used to get all the Intune Data Warehouse Collection Name .Author Original Source: Microsoft with small modifications by David Maiolo .DESCRIPTION The function connects to the Data Warehouse URL and returns all Collection Name .EXAMPLE Get-IntuneDataWarehouseCollectionNames Returns all Data Warehouse Collection names .NOTES NAME: Get-IntuneDataWarehouseCollectionNames #>
    [cmdletbinding()]
    param
    (
    )

    if (!$global:intuneWarehouseAuthResult) {
        throw "No authentication context. Authenticate first by running 'Connect-IntuneDataWarehouse'"
    }

    try {
        $headers = @{
                    'Content-Type'='application/json'
                    'Authorization'="Bearer " + $global:intuneWarehouseAuthResult.AccessToken
                    'ExpiresOn'= $global:intuneWarehouseAuthResult.ExpiresOn
                    };
        $Collections = Invoke-WebRequest -Uri $global:intuneWarehouseURL -Method Get -Headers $headers
        $AllCollections = ($Collections.content | ConvertFrom-Json).value.name | sort
        return $AllCollections
    }
    catch {
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -f Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        throw
    }
}

function Test-IntuneDataWarehouseAuthentication {
<# .SYNOPSIS Tests whether or not the current authentication context is valid. Original Source: IntuneDataWareHouseCmdlets / Microsoft .Author Original Source: Microsoft with small modifications by David Maiolo .DESCRIPTION The function tests whether or not the current authentication context is valid Optionally pass a user to scope it down further. If this returns $False, then Connect-IntuneDataWarehouse should be run. .EXAMPLE Test-IntuneDataWarehouseAuthentication Test-IntuneDataWarehouseAuthentication -User nick@example.com .NOTES NAME: Test-IntuneDataWarehouseAuthentication #>
    [cmdletbinding()]
    param
    (
        $User
    )

    $isAuthValid = $False

    if ($global:intuneWarehouseAuthResult) {
        # Setting DateTime to Universal time to work in all timezones
        $DateTime = (Get-Date).ToUniversalTime()

        # If the authToken exists checking when it expires
        $TokenExpires = ($global:intuneWarehouseAuthResult.ExpiresOn.datetime - $DateTime).Minutes

        if ($TokenExpires -gt 0 -and (!$User -or $User -eq $global:intuneWarehouseAuthUser)) {
            $isAuthValid = $True
        }
    }
    return $isAuthValid
}

function Get-IntuneDatawarehouseCollections {
<# .SYNOPSIS This function is used to retrieve the Datawarehouse collections .Author David Maiolo. Concepts from Microsoft .NOTES NAME: Get-IntuneDatawarehouseCollections #>
    [cmdletbinding()]
    param
    (
    )

    if (!$global:intuneWarehouseAuthResult) {
        throw "No authentication context. Authenticate first by running 'Connect-IntuneDataWarehouse'"
    }

    try {
        $headers = @{
                    'Content-Type'='application/json'
                    'Authorization'="Bearer " + $global:intuneWarehouseAuthResult.AccessToken
                    'ExpiresOn'= $global:intuneWarehouseAuthResult.ExpiresOn
                    };
        $Collections = Invoke-WebRequest -Uri $global:intuneWarehouseURL -Method Get -Headers $headers
        #$AllCollections = ($Collections.content | ConvertFrom-Json).value.name | sort
        $AllCollections = ($Collections.content | ConvertFrom-Json).value | sort
        return $AllCollections
    }
    catch {
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        throw
    }
}

#Functions Specific TO Microsoft Graph

function Get-AuthToken {

<# .SYNOPSIS This function is used to authenticate with the Graph API REST interface .DESCRIPTION The function authenticate with the Graph API Interface with the tenant name .EXAMPLE Get-AuthToken Authenticates you with the Graph API interface .NOTES NAME: Get-AuthToken #>

[cmdletbinding()]

param
(
    [Parameter(Mandatory=$true)]
    $User
)

$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User

$tenant = $userUpn.Host

Write-Host "Checking for AzureAD module..."

    $AadModule = Get-Module -Name "AzureAD" -ListAvailable

    if ($AadModule -eq $null) {

        Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable

    }

    if ($AadModule -eq $null) {
        write-host
        write-host "AzureAD Powershell module not installed..." -f Red
        write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
        write-host "Script can't continue..." -f Red
        write-host
        exit
    }

# Getting path to ActiveDirectory Assemblies
# If the module count is greater than 1 find the latest version

    if($AadModule.count -gt 1){

        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]

        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }

            # Checking if there are multiple versions of the same module found

            if($AadModule.count -gt 1){

            $aadModule = $AadModule | select -Unique

            }

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

    else {

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null

[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"

$redirectUri = "urn:ietf:wg:oauth:2.0:oob"

$resourceAppIdURI = "https://graph.microsoft.com"

$authority = "https://login.microsoftonline.com/$Tenant"

    try {

    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

    # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
    # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession

    $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"

    $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")

    $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result

        # If the accesstoken is valid then create the authentication header

        if($authResult.AccessToken){

        # Creating header for Authorization token

        $authHeader = @{
            'Content-Type'='application/json'
            'Authorization'="Bearer " + $authResult.AccessToken
            'ExpiresOn'=$authResult.ExpiresOn
            }

        return $authHeader

        }

        else {

        Write-Host
        Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red
        Write-Host
        break

        }

    }

    catch {

    write-host $_.Exception.Message -f Red
    write-host $_.Exception.ItemName -f Red
    write-host
    break

    }

}

function Get-MicrosoftGraphMainCategories{
<# .SYNOPSIS This function is used to provide the Main applicaiton menu what to display and Data warehouse collections to call when invoked. .Author David Maiolo .NOTES NAME: Get-MicrosoftGraphMainCategories #>

    $GraphCategories = @()
    $GraphCategories += [PSCustomObject] @{'name' = 'Show-Command Get-MicrosoftGraphRESTRequest'; 'DisplayName' = 'Run Custom Graph Request'};
    $GraphCategories += [PSCustomObject] @{'name' = 'Get-MicrosoftGraphRESTRequest -graphApiVersion "Beta" -Resource "deviceAppManagement/mobileApps"'; 'DisplayName' = 'Run Sample Graph Request: Mobile Apps'};

    return $GraphCategories
}

function Get-MicrosoftGraphRESTRequest{
<# .SYNOPSIS This function is used to call your Microsoft Graph request of choice and return the results as an array. .Author David Maiolo .NOTES NAME: Get-MicrosoftGraphRESTRequest #>
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
        [ValidateSet ("Beta","v1.0")]
        [string]$graphApiVersion,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
        [String]$Resource,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)]
        [ValidateSet ("count","expand","filter","format","orderby","search","select","skip","skipToken","top")]
        [String]$QueryType,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=3)]
        [String]$Query

    )

    try{
        if(([bool]($MyInvocation.BoundParameters.Keys -contains 'Query')) -and ([bool]($MyInvocation.BoundParameters.Keys -contains 'QueryType'))){
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)?`$$QueryType=$Query"
        }else{
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)"
        }
        Write-Host "Requesting $uri..."

        $GraphRequestValue = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value | ? { ($_.'@odata.type').Contains("managed") }

        return $GraphRequestValue

    }catch{
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        break

    }

}

#Universal Functions

function Convert-VariableNameToFriendlyName{
<# .SYNOPSIS This function is used to convert a variable name, such as helloMyNameIsDavidMaiolo to a friendly variable name, such as Hello My Name Is David Maiolo. .Author David Maiolo .NOTES NAME: Convert-VariableNameToFriendlyName #>
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
        [String]$VariableName
    )

    $FriendlyVariableName = ($VariableName -split '_')[0] -creplace '(?<=\w)([A-Z])', ' $1'
    $TextInfo = (Get-Culture).TextInfo

    $FriendlyVariableName = $TextInfo.ToTitleCase($FriendlyVariableName)

    return $FriendlyVariableName
}

function Get-IntuneMainMenu {
<# .SYNOPSIS This function is used to provide a main menu for the application. This was written generically and can be used for other applications. .Author David Maiolo .NOTES NAME: Get-IntuneMainMenu #>
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   $Collections,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
                   [String]$CollectionName,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)]
                   [Switch]$UseSubMenu
    )

    #if (!($global:intuneWarehouseAuthResult) -or !($global:authToken)) {
    #    throw "No authentication context. Authenticate first!"
    #}
    try {
        do {
            clear
            Write-Host "========================================="
            Write-Host $CollectionName
            Write-Host "========================================="
            #$menu = @{}
            $MenuArray = @()
        
            for ($i=1;$i -le $collections.count; $i++) { 
                Write-Host "[$i] $($collections[$i-1].DisplayName)"
                if($i -eq $Collections.count){
                    $iplusone = ($i+1)
                    Write-Host "[$iplusone] Exit"
                }
            }
            [int]$ans = Read-Host 'Enter Selection'

            if ($ans -eq ($collections.count+1)){
                break
            }else{
                $collection = Invoke-Expression $collections[$ans-1].Name
                #Write-Host Colection: $collection
                if(([bool]($MyInvocation.BoundParameters.Keys -contains 'UseSubMenu'))){
                    Get-IntuneSubMenu -Collections $collection -CollectionName $collections[$ans-1].DisplayName
                }else{
                    return $collection
                }
            }
        }while($ans -ne ($collections.count+1))
    }catch{
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        write-host
        sleep 10
        break
    }
}

function Get-IntuneSubMenu {
<# .SYNOPSIS This function is used to provide a sub menu for the application. This was written generically and can be used for other applications. .Author David Maiolo .NOTES NAME: Get-IntuneSubMenu #>
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
                   $Collections,
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
                   [String]$CollectionName
    )

    #$collections = Get-IntuneDatawarehouseCollections

    if (!$global:intuneWarehouseAuthResult) {
        throw "No authentication context. Authenticate first by running 'Connect-IntuneDataWarehouse'"
    }

    try {
        do{
            clear
            Write-Host ">>>>>$CollectionName                          " -ForegroundColor Cyan
            Write-Host ">>>>>=========================================" -ForegroundColor Cyan
            $menu = @{}
            for ($i=1;$i -le $collections.count; $i++) { 
                Write-Host "     [$i] $($collections[$i-1].DisplayName)" 
                $menu.Add($i,($collections[$i-1].name))
                if($i -eq $Collections.count){
                    $iplusone = ($i+1)
                    Write-Host "     [$iplusone] Go Back"
                    $menu.Add($i+1,("GoBack"))
                }
            }
        
            [int]$ans = Read-Host 'Enter Selection'
            $selection = $menu.Item($ans);
        
            if ($selection -eq "GoBack"){
                #Menu
                #$Collections = Get-IntuneDatawarehouseMenuCategories
                #Get-IntuneMainMenu -Collections $Collections -CollectionName "InTune Management"
                return
            }else{
                Get-IntuneDataWarehouseCollection $selection | Out-GridView -Title (Convert-VariableNameToFriendlyName -VariableName $selection) 
                #Read-Host -Prompt "Press Enter to continue" 
            }
        }while($selection -ne "GoBack")

    }catch{
        $ex = $_.Exception
        $errorResponse = $ex.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($errorResponse)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Host "Response content:`n$responseBody" -ForegroundColor Red
        Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
        write-host
        sleep 10
        break
    }
}