by David Maiolo 2018-03-28

Microsoft Azure Cloud Computing Platform and Services

In this article I’ll walk you through an overview of Microsoft Azure, and some of the cloud services it offers. We’ll also take a look at some of the basics of Cloud Computing and how they relate to Microsoft Azure.

Cloud Computing Overview

Cloud computing is the delivery of computing services such as servers, storage, databases, networking, software, and analytics via the internet. Any company offering one of these cloud services are called cloud providers and very often charge for the services centered on usage. This is similar to how a water or electric billing company’s pricing methodology works. The three key cloud providers in the market today are Amazon Web Services, Google Cloud Platform and Microsoft Azure.

Cloud Characteristics

There are really five main characteristics that define a cloud.

On-Demand Self-Service: Clouds allows you to provision and de-provision your resources on-demand through a web portal or application. This is handled in an automated fashion without the need for human help.

Ubiquitous Network Access: Clouds can be accessed from anywhere over the internet using thin or thick clients such as smartphones, laptops or a PC.

Resource Pooling: Cloud resources are pooled so that they can dynamically assigned, reassigned and unassigned per requirement.

Rapid Elasticity: Clouds provide the ability to rapidly expand and contrast resources based on user demands.

Measured Service: Clouds allow consumers to only pay for the computing resources they have used. As mentioned earlier, this concept is similar to utilities like water or electricity.

Cloud Servicing Models

The three main servicing models of a cloud are Software as a Service (SaaS), Platform as a Service (PaaS) and Infrastructure as a Service (IaaS). These models divide the ownership of resources under the model. There is also a newer model, Function as a Service (FaaS), which I will touch on briefly.

With Infrastructure as a Service (IaaS), you manage everything but the actual infrastructure. IaaS provides you the computing infrastructure, physical or (quite often) virtual machines and other resources like the virtual-machine disk image library, block and file-based storage, firewalls, load balancers, IP addresses and virtual local area networks.

Examples: Amazon EC2, Microsoft Azure, Rackspace, Google Compute Engine.

Platform as a Service (PaaS) provides computing platforms which typically include the operating system, programming language execution environment, database and a web server.

Examples: AWS Elastic Beanstalk, Microsoft Azure, Heroku, Force.com, Google App Engine, Apache Stratos.

The Software as a Service (SaaS) model provides you with access to application software often referred to as “on-demand software”. You don’t have to worry about the installation, setup and running of the application. The cloud provider will do that for you. You just have to pay and use it through some client.

Examples: Google Apps, Gmail, Microsoft Office 365.

There is also a rather recent cloud service model called Function as a Service (FaaS). FaaS provides a platform to develop, run, and manage application functionalities without the complexity of building and maintaining the infrastructure typically associated with developing and launching an app. Building an application following this model is one way of achieving a “serverless” architecture, and is typically used when building microservices applications.

Serverless computing can also be achieved using the PaaS model. These models are different in their implementation architecture, which has implications for scaling. In most PaaS systems, the system continually runs at least one server process and, even with auto scaling, a number of longer running processes are simply added or removed on the same machine. In a FaaS model, the functions are expected to start within milliseconds in order to allow handling of individual requests.

Cloud Deployment Models

Public Cloud: allow computing resources to be used by different organizations through public Internet on a pay as you go model. Cloud providers ensure some sort of separation for resources used by different organizations. This is known as multitenancy.

Private Cloud: is owned by an individual organization and maintained either by this organization or a third party and can be located on site or off-site. Computing resources are behind the corporate firewall. An example of this would be System Center Virtual Machine Manager (SCVMM).

Community Cloud: is owned and shared by multiple organizations with a shared interest.

Hybrid cloud: is the combination of any type of cloud model mentioned above.

Benefits of Cloud Computing

Cost: Clouds eliminate the need to buy H/W, S/W, and setting up and running On-Site datacenters.

Speed: Clouds provide agility in provisioning computing resources via Self-Service features.

Global Scale: Clouds allow you to scale elastically from any geographic location.

Productivity: Removes the need to “rack and stack” and lets you focus your time on achieving the goals of your end project.

Reliability: Performs data backup, disaster recovery and business continuity easier and less expensively.

Performance: Provided reduced network latency.

Overview of Microsoft Azure

Azure is a growing collection of Microsoft cloud computing services for building, testing, deploying, and managing applications and services through a global network of Microsoft-managed data centers. It provides software as a service (SaaS), platform as a service (PaaS) and infrastructure as a service (IaaS) and supports different programming languages, tools and frameworks, including Microsoft-specific and third-party software and systems.

Azure has expanded to over 50 regions and is available in 140 countries.

Azure was announced in 2008 and released 2010 as “Windows Azure” and was renamed to “Microsoft Azure” in 2014.

Azure Advantages

Azure has made significant advances over the years and offers features that currently far surpass many of the competitors (Amazon and Google) such as:

  • PaaS Capabilities (such as IIS Server)
  • .Net Compatibility
  • Security Offerings
  • Hybrid Solutions for Seamless Cloud Connectivity
  • Integrated Environment
  • Gentle Learning Curve (lots of online learning available)
  • The ‘Enterprise Agreement’ Advantage

Azure Domains and Services

Azure has been constantly growing its list of integrated services, features, and bundled suites since it became public. Let’s take a look at some of the major domains Azure handles and the services offered in those domains.

Top Services by Cloud Models

  • Top Azure IaaS services: Containers and Virtual Machines.
  • Top Azure PaaS Services: App Services, Bing Search and Azure CDN.
  • Top Azure SaaS Services: Office 365, Microsoft Intune, and Microsoft Dynamics 365.

Containers

Microsoft has recently stated the increasing importance of containers as a part of the Azure model. Broadly, containers are operating system virtualizations as opposed to virtual machines being machine virtualizations. Containers are largely being stated as the new form of virtualization by Microsoft. 10 years ago, machine virtualization changed how we deployed applications and services in the data center. The importance of virtual machines will likely continue, however operating system virtualization with Azure containers has started to penetrate the market.

Machine virtualization, such as Hyper-V, creates a partition for each OS deployment. Each partition essentially simulates a machine using software and gives the impression of multiple physical machines running instead of the actual single host. However, for each application you might install, you’ll have one or more VMs, each with its own OS to configure, patch, and maintain. No matter how much the deployment process is optimized, a large file copy will take place and an OS specialization will take time to run.

Containers are a newer type of virtualization called operating system virtualization. Whereas machine virtualization takes a single machine and simulates several machines, containers take a single operating system installation and simulate several operating system installations.

The following container services are available in Azure:

  • Container Registry: Store and manage container images across different types of Azure deployments
  • Container Instances: Create new containers with a command
  • Service Fabric: Develop microservices and orchestrate containers on Windows/Linux
  • Azure Container Service (AKS): Simplify the deployment, management, and operations of Kubernetes

Compute

Compute resources refer to the hosting model for the computing resources that an Azure application will run on. Within Intrastructure-as-a-Service (IaaS), the VMs are provisioned along with associated network and storage components.

Platform-as-a-Service (PaaS) affords a managed hosting environment, where the deployment of applications do not require the management of VMs or networking resources. The App Service is an example of a PaaS service under the Computer domain.

Functions-as-a-Service (FaaS) is the final step in removing administration from the computer domain. Rather than create compute instances and then deploy code, code is simply deployed, and the service runs it automatically. This is known as serverless architecture, and scales up or down to the level required for traffic. Functions are a FaaS service.

The following compute resources are available in Azure:

  • Virtual Machines: Provision Windows and Linux virtual machines
  • Virtual Machine Scale Sets: Manage and scale Linux and Windows virtual machines (create thousands of identical virtual machines in minutes)
  • App Service: Create cloud apps for web and mobile
  • Functions: Process events with serverless code
  • Batch: Job scheduling and compute management
  • Container Instances: Run containers
  • Service Fabric: Develop microservices and orchestrate containers on Windows or Linux
  • Azure Container Service (AKS): Deployment, management, and operations of Kubernetes
  • SQL Server on Virtual Machines: Host SQL Server apps in the cloud

Networking

Software-defined networking is a domain of the Azure infrastructure-as-a-Service (IaaS) scenario. Some networking services provided by Azure are:

  • ExpressRoute: Dedicated private network fiber connections to Azure
  • Azure DNS: Host a DNS domain in Azure
  • Virtual Network: Provision private networks, optionally connect to on-premises datacenters
  • Traffic Manager: Route incoming traffic for high performance and availability
  • Load Balancer: Deliver high availability and network performance to applications
  • VPN Gateway: Establish secure, cross-premises connectivity
  • Network Watcher: Network performance monitoring and diagnostics tool

Storage

Azure Storage falls within a domain that provides highly available, secure, durable, scalable, and redundant storage. Azure Storage comprises three primary services: Blob storage, File storage, and Queue storage.

  • Blob Storage: REST-based object storage for unstructured data
  • File Storage: File shares that use the standard SMB 3.0 protocol
  • Queue Storage: Scale apps according to traffic. Queue messages can be up to 64 KB in size, and a queue can contain millions of messages.
  • Managed Disks: Persistent, secured disk storage for Azure virtual machines
  • Data Lake Store: Hyperscale repository for big data analytics workloads

Web + Mobile

The web and mobile domain both compromise app development within Azure. The Web Apps services make use of .NET, Java, Node.js, PHP, and Python on Windows or .NET Core, Node.js, PHP or Ruby on Linux. The Mobile Apps service is designed to make apps for iOS, Android, Windows, or Mac, each of which has been tested on a mock server.

  • API Management: Publish APIs to developers
  • Web Apps: Create and deploy Web apps at scale
  • Mobile Apps: Build and host the backend for mobile app

Databases

A cloud database is a database that runs on a cloud computing platform, and access to it is provided as a service. The most notable Azure database service is the Azure SQL Database service, which is a managed cloud database (SaaS) provided as part of the Databases domain.

  • Azure SQL Database: Relational SQL database as a service providers
  • Azure Cosmos DB: Globally distributed, multi-model database
  • SQL Data Warehouse: Data warehouse as a service
  • Azure Database for MySQL: MySQL database service for app developers

Analytics

The idea behind the Analytics services is to provide solutions that turn big-data into actionable insights which are meant to gather, store, process, analyze, and visualize data of any variety, volume, or velocity.

  • HDInsight: Provision cloud Hadoop, Spark, R Server, HBase, and Storm clusters
  • Apache Spark for Azure HDInsight: Apache Spark in the cloud
  • Apache Storm for HDInsight: Real-time stream processing for big data
  • R Server for HDInsight: Predictive analytics, machine learning, and statistical modeling for big data
  • Stream Analytics: Real-time data stream processing from millions of IoT devices
  • Event Hubs: Receive telemetry data from devices
  • Data Factory: Hybrid data integration
  • Azure Analysis Services: Analytics engine as a service
  • Azure Databricks: Apache Spark-based analytics platform
  • Power BI Embedded: Data visualizations for applications

AI + Machine Learning

Machine learning is a method that allows an algorithm to use existing data to forecast future behaviors, outcomes, and trends. Machine learning lets a program learn without being overtly programmed. Azure Machine Learning is a cloud predictive analytics service that makes it possible to create and deploy predictive models as analytics solutions.

  • Machine Learning Studio: Build, deploy, and manage predictive analytics
  • Text Analytics API: Evaluate sentiment and topics
  • Project Academic Knowledge: Academic content from the Microsoft Academic Graph
  • Computer Vision API: Actionable information from images
  • Content Moderator: Automated image, text, and video moderation
  • Emotion API: Personalize user experiences with emotion recognition
  • Face API: Detect faces in photos
  • Bing Speech API: Convert speech to text and back
  • Bing Autosuggest API: Autosuggest options for searches
  • Bing Spell Check API: Detect and correct spelling mistakes
  • Translator Speech API: Speech translation with a REST API call
  • Translator Text API: Machine translation with a REST API call
  • Bing Web Search API: Search details from web documents
  • Bing Video Search API: Search for videos
  • Bing Image Search API: Search for images
  • Bing News Search API: Search for news

Security + Identity

Also known as identity and access management solutions, Security + Identity services are designed to guard access to applications and resources from On-Premises and cloud data, through services such as multi-factor authentication and conditional access policies.

Example of recommended classification for Azure Information Protection

  • Azure Active Directory: Synchronize on-premises directories and enable single sign-on
  • Multi-Factor Authentication: Extra authentication step for applications
  • Azure Information Protection: Protect your sensitive information
  • Azure Active Directory Domain Services: Join Azure virtual machines to a domain without domain controllers
  • Key Vault: Maintain control of keys and other secrets

Management Tools

The primary tool for managing Azure is the Azure Portal where services are available to be added as favorite to the portal.

  • Microsoft Azure portal: Build, manage, and monitor all Azure products
  • Backup: Server backup to the cloud
  • Scheduler: Run jobs on recurring schedules
  • Log Analytics: Collect machine data from on-premises and cloud
  • Traffic Manager: Route incoming traffic for high performance and availability
  • Azure Monitor: Monitoring data for Azure resources
  • Security & Compliance: Threat detection and prevention
  • Insight & Analytics: Search, correlate, and analyze data from the cloud
  • Network Watcher: Network performance monitoring and diagnostics tool
  • Cloud Shell: Browser-based shell (PowerShell Command-lets)
  • Azure mobile app: Azure resources via mobile app
  • Azure Policy: Implement corporate governance and standards

Microsoft Desktop Optimization Pack

Microsoft Desktop Optimization Pack (MDOP) is a suite of utilities that is part of the subscription service Microsoft Software Assurance program.

The MDOP suite is grouped into 3 technologies: Virtualize, Manage and Restore

Virtualize

Microsoft Application Virtualization (App-V)

Microsoft Application Virtualization (App-V) 5 lets you make applications available to users without having to install the applications directly on the computers. App-V captures applications into centrally managed services that are not installed and don’t conflict with other applications.

Starting with Windows 10 1607 (Anniversary Update), this item became part of Windows 10 and is no longer included in MDOP.

Create the Virtualization Package in the Application Virtualization Sequencer

Then deploy it as you would an application in Configuration Manger:

Microsoft User Experience Virtualization (UE-V)

A replacement for roaming profiles that will allow user preferences and settings to roam between different environments and types of devices. 

Starting with Windows 10 (Anniversary Update), this item became part of Windows 10 and is no longer included in MDOP.

Microsoft Enterprise Desktop Virtualization (MED-V)

Microsoft Enterprise Desktop Virtualization (MED-V) uses Microsoft Virtual PC to provide an enterprise solution for desktop virtualization. With MED-V, you create, deliver, and manage corporate Virtual PC images on Windows-based desktop.

Microsoft Advanced Group Policy Management (AGPM)

Provides enhanced management of group policy and integrates with the Group Policy Configuration MMC snap-in. It adds change control, offline editing, and delegation capabilities.

Microsoft BitLocker Administration and Monitoring (MBAM)

This tool for managing, enforcing and monitoring BitLocker drive encryption. MBAM is accessed as an administration console via a web browser, as well as an agent which must be installed on every computer in the enterprise.

Restore

Microsoft Diagnostics and Recovery Toolset (DaRT)

Microsoft Diagnostics and Recovery Toolset (DaRT) 10 is a tool to diagnose and repair a computer that cannot be started. It was formerly called Emergency Repair Disk Commander (ERD Commander – Winternals) and diagnoses an offline copy of Windows through a bootable WinPE environment

Microsoft DaRT is now referred to as the Windows Recovery Environment and comes standard with Windows 10.

Microsoft Intune

By dmaiolo 2018-03-14

This article is part of a six-part series I have put together on Microsoft Intune.

  1. History of Microsoft Intune
  2. Intune Overview
  3. Mobile Device Management (MDM)
  4. Conditional Access
  5. Policies
  6. Mobile Application Management (MAM)

Intune Overview

Enterprise Mobility + Security (EMS)

Microsoft’s flagship mobility management product is called Enterprise Mobility + Security (EMS) suite. Intune can be purchased as a stand-alone subscription or as part of EMS, which acts as a single license to use:

  • Intune: Device and App Management: Manage and protect apps and data across multiple devices
  • Azure Active Directory Premium: Identity & Access Management: Manage identities across On-Premises and cloud. Single sign on and self-service for any application.
  • Azure Rights Management Services: Information Protection: Encryption, identity and authorization protects files and email
  • Advanced Threat Analytics: Behavior based threat analytics: Identify suspicious activities and advanced threats.

The core Enterprise Mobility Management (EMM) functionality of EMS is provided by Intune, responsible for Mobile Device Management (MDM), Mobile Application Management (MAM) and a separate level of PC management separate from SCCM (driven from the cloud).

Enterprise Mobility Management (EMM)

Enterprise Mobility Management (EMM) is a term that describes the process of managing mobile devices, wireless networks, and other mobile computing services in an enterprise environment. As enterprises integrate more smartphone and tablet devices into the workplace, EMM has become increasingly significant. From an EMS perspective, Intune is the EMM.

Mobile Device Management (MDM) Intune

Mobile device management (MDM) is a term for the administration of mobile devices, such as smartphones, tablet computers, laptops and desktop computers. Intune can manage both corporate devices and a “bring your own device” (BYOD). MDM allows you to control management, inventory, app deployment, provisioning, and retirement.

Mobile Application Management (MAM) Intune

Mobile application management (MAM) envelops the services responsible for provisioning and controlling access to mobile apps used in an enterprise settings on corporate owned devices and BYOD..

Intune leverages MAM to set App Protection Policies at the app level for use with or without MDM device enrollment. Intune’s MAM helps protect corporate data with the policies that restrict data outflow such as Copy, Paste, Save As, provide encryption at rest, enforce application access and compliance, and remove corporate data at the application level.

Conditional Access Intune

Intune allows you to manage access to corporate data by ensuring that only managed and compliant devices, aka “Healthy” devices, are able to access corporate email and files. If the device is not managed by Intune or compliant with IT policies (such as password strength, encryption, OS version), the access is blocked.

PC Management Intune

In addition to managing mobile devices, Intune also manages Windows 10 computers using the Intune agent or via MDM. Intune also works with System Center Configuration Manager to support more advanced PC and server management scenarios.

Mobile Content Management (MCM)

A Mobile Content Management system (MCMs) stores and delivers content and services to mobile devices, such as ringtones, games, text-messaging and news in the business-2-consumer market. From Microsoft’s vantage point, this service is handled by Microsoft OneDrive.

Mobile Information Management (MIM)

Mobile information management (MIM) is a generic security strategy that involves keeping sensitive data encrypted and allowing only approved applications to access or transmit it. From Microsoft’s vantage point, this service is handled by Microsoft OneDrive

Identity

Intune leverages its identity component through Azure Active Directory (AD) with options for the directory source to be cloud only or to sync to On-Premises. Azure AD provides the area to store all of the users that are used within Intune. In other words, any users in Intune are actually just users in Azure AD.

If you already use and have users within Azure AD with Office 365, Intune can leverage the same Azure AD database so that you do not have to recreate it.

Hybrid Integration

The first thing to think of when setting up Intune is who should be the MDM authority: Intune, SCCM or Intune + SCCM (co-management). Once the MDM authority is set, Intune is hard configured in that mode and reversing to a different mode requires a support call into Microsoft.

On-Premises Connectors

The most popular On-Premises connector is the Microsoft Exchange Connector, which uses Conditional Access to make sure the device is compliance before a device can access email. The on-premises Exchange connector is also responsible for discovering mobile devices that connect to on-premises Exchange Servers by synchronizing the existing Exchange Active Sync (EAS) record with Intune.

Another popular On-Premises connector is the Microsoft Intune Certificate Connector which is used to configure your infrastructure, then create and assign Simple Certificate Enrollment Protocol (SCEP) certificate profiles with Intune

Device Applicability (Configuration Service Providers)

One of the things you’ll notice when configuring policies within Intune is that not every policy supports every device. Configuration service providers (CSPs) expose device configuration settings in Windows 10 and determine what is possible to manage on the device.

A CSP is an interface in Windows 10 between configuration settings specified in a provisioning document and configuration settings on the device. They function similar to Group Policy in that they provide an interface to read, set, modify, or delete configuration settings for a given feature. Typically, these settings map to registry keys, files or permissions, where some are configurable and some are read-only.

User Roles

Role Bases Administration Control (RBAC) is built right into Azure AD and Intune and provides different roles available to manage things within the Intune tenant.

Role Definitions

Role definition: The name of a role, the resources it manages, and the permissions granted for each resource.
Members: The user groups that are granted the permissions.
Scope: The user or device groups that the members can manage.
Assignment: When the definition, members, and scope have been configured, the role is assigned.

Built-In Roles (from Azure AD)

Global Administrator: Access to all administrative features in Azure AD, as well as services that federate to Azure AD like Exchange Online, SharePoint Online, and Skype for Business Online.
Intune Service Administrator: Global permissions within, manage users, devices, and create and manage Intune groups.
Conditional Access Administrator: Only have permissions to view, create, modify, and delete conditional access policies.

Built-In Roles (Native to Intune)

Help Desk Operator: Performs remote tasks on users and devices, and can assign applications or policies to users or devices.
Policy and Profile Manager: Manages compliance policy, configuration profiles, Apple enrollment, and corporate device identifiers.
Read Only Operator:
Views user, device, enrollment, configuration, and application information. Cannot make changes to Intune.
Application Manager:
Manages mobile and managed applications, and can read device information.
School Administrator:
Manages Windows 10 devices in Intune for Education.

Special Permissions

When the Device Enrollment Administrator permission is added to an Intune Role, members of that role can enroll more than the 5 device maximum.

Licensing Devices for Intune (Enabling Use)

Users must be enabled for device enrollment before they can begin enrolling devices into Intune. In Intune Standalone you can enable users individually (or with PowerShell) and with Hybrid Configuration Manager you select a collection for enrollment.

The purpose of requiring you to enable each user is to give you the granularity to control who can and cannot enroll devices as a large enterprise environment might have thousands of users.

Microsoft Intune

By dmaiolo 2018-03-14

This article is part of a six-part series I have put together on Microsoft Intune.

  1. History of Microsoft Intune
  2. Overview
  3. Mobile Device Management (MDM)
  4. Conditional Access
  5. Policies
  6. Mobile Application Management (MAM)

History of Microsoft Intune

The core idea of Intune started in 2010 with a desire to be able to manage Windows XP and Window 7 computers from the cloud, so that an enterprise wouldn’t have to deploy any infrastructure in their environment. These early conceptual ideas were tested through “Intune Waves”. After these first early waves of Microsoft Intune, Microsoft’s Product Management team found that the internet facing devices that most enterprises were really trying to manage were not PCs, but were mobile devices. Microsoft tried to shift the focus from PC management to mobile device management with Exchange ActiveSync (EAS) and inbuilt MDM hooks that already existed within iOS, and then not much later the MDM hooks integrated into Windows devices. Microsoft wanted to combine the two efforts and make Intune a solution that could manage both mobile devices and PCs from the cloud.

So many PCs in the enterprise world were already being managed successfully with Microsoft’s System Center Configuration Manager (SCCM), so Intune began to initially integrate cloud management into the Configuration Manger service. However, rather than just run with that concept and put Configuration Manager in the cloud with Intune for PC device management, Microsoft has worked to create Intune as a standalone Mobile Device and PC management cloud solution that operates with principles that make it especially suited for a cloud environment.

One of the largest needs for this fork centers around Configuration Manger’s fundamental use of the SCCM client on each PC. Mobile devices, on the other hand, largely need to be managed over protocols to devices because the SCCM client simply cannot be installed onto them (such as an Android or iOS). This, fundamentally, represented a real change in the architecture of the two systems where Microsoft then developed Intune as a PC management solution that adhered to the mobile device cloud strategy of communication over protocol.

Another reason for the shift towards a new, independent, solution for cloud PC management in Intune is Configuration Manager’s fundamental design around PC management of devices within a WAN topology, as opposed to a cloud topology, like it had already been doing with mobile devices. As a result, PC management in Intune started with a whole new code-base that separated itself entirely from Configuration Manger. No longer was it a design requirement to worry about SQL databases, server cores, and bandwidth requirements, but rather build a system that could scale-out within the internet on top of Azure.

Intune today is a part of a full-scale cloud Enterprise called Enterprise Mobility + Security (EMS) suite, which incorporates Device and App Management, Identity & Access Management, Information Protection and Behavior based threat analytics.

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