Intune Data Warehouse Connector Application

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. 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
    }
}

Leave a Comment

Your email address will not be published.