We all have probably been there and developed a PowerShell script that took some fair amount of time until the execution completed, weren’t we? Of course one could argue and say that as long a script ‘works’ it is good enough but depending on the use case and environment a PowerShell script that runs 30 to 60 minutes exceeds the patience of most (IT) people and can also lead to increased costs. But what makes those kinds of scripts that awfully slow and can’t we just tweak them to run faster?
The following examples and script will be related to PowerShell and the Microsoft Graph API but the mentioned approaches can be adapted for any kind of RESTful API and scripting language. For all interactions with the Graph API I use the Microsoft Graph PowerShell SDK which is available as a PowerShell module from the PowerShell gallery.
Reasons why your script is slow # I would say I have already read and (tried to) understand hundreds of PowerShell scripts out there and I often notice the following flaws which lead to poor performance:
Slow algorithms and wrong data structures # Slow algorithms are extricably linked to the understanding of data structures when it comes to PowerShell scripting. Here the top two cases I often observe:
When you are new to RESTful APIs and want to start with Microsoft Graph to automate tasks in your Endpoint Manager tenant all the stuff about app registrations, access tokens, pagination and request headers can be quite confusing. In this post I want to show you a quick tip to kickstart your Microsoft Graph API experience.
Requirements # Cloud admin account with Intune Administrator role assigned Ability to install Modules from the PowerShell gallery JWT # Just because you can’t see it… doesn’t mean it isn’t there: Due to the naturality of OAuth 2.0 and OpenID connect (these are the protocols involved for authorization and authentication in a cloud environment) we can capture short lived access tokens, also called Json Web Tokens (JWTs) directly from a browser. Tokens are usually valid between 50 and 60 minutes - just what we need to get some hands on with Microsoft Graph API and MEM automation.
The cool thing is actually that we don’t need any kind of app registration or additional permissions which can be quite some blocker in certain restricted environments (or staff unfamiliar with those technologies 😉).
App protection (also called MAM) policies have been around for a couple of years within MEM and I already used them in various projects to protect company data on unmanaged iOS and Android devices. One of the drawbacks with this approach is that we do not have full visibility about the usage and I tried to shed some light about this with a PowerShel reporting script that pulls data from the Microsoft Graph API.
Information visible within the portal # In the MEM portal we can find report data about the number of users that have checked-in to any MAM policy grouped by the respective app.
If we want to perform a wipe we will also be able to see the devices a user has registered:
Of course I was curious which additional data is available on the Microsoft Graph API and found the following resource storing app protection policy check in details: /users/{ID}/managedAppRegistrations.
Script # The script uses the Intune PowerShell SDK (could easily be ported to MSAL.PS because I wrote it already a couple of months ago) to enumerate all internal users within the tenant and will check the above mentioned managedAppRegistrations resource. At the end you are presented a flattened CSV report containig the following details:
Based on one of my older posts about PowerShell script signing with Azure DevOps I recently implemented a PowerShell script signing workflow with GitHub actions I wanted to share with the community.
Prerequisites # For this post the following prerequisites are required:
Code signing certificate in PFX format GitHub account Add variables to GitHub actions # Because GitHub variables can only be of string content we need to get the contents of the pfx file as base64 encoded string:
$pfxCertFilePath = "~\Downloads\CodeSigningCertificate.pfx" $pfxContent = Get-Content $pfxCertFilePath -Encoding Byte [System.Convert]::ToBase64String($pfxContent) | Set-Clipboard GitHub action variables # Add two variables as actions secrets:
BASE64_PFX: Base 64 encoded string of the PFX (automatically copied into your clipboard with the above commands) PFX_PASSWORD: Password for the private key of the pfx file
GitHub action workflow # Place the following workflow file within your git repository: .github/workflows/SignPowerShell.yaml whereas the name of the YAML file can be freely chosen.
The Send-MailMessage cmdlet has been around for a couple of years and is mostly used to send email messages from PowerShell. But with the deprecation and security flaws of legacy authentication it’s time for a better option which actually supports modern authentication. For this purpose we can use the Microsoft Graph API and the Microsoft Graph PowerShell SDK. The best thing is that this solution works without any service account and does not need any exclusions from conditional access.
Microsoft Graph resource # To send a mail we simply specify the user account from which we want to send the email:
POST: https://graph.microsoft.com/v1.0/users/sean.connery@dev.nicolonsky.ch/sendMail Create an app registration # Simply create a new app registration with the Mail.Send permissions and use a certificate for the authentication.
We need to take additional steps to limit the permissions of the app registration. Otherwise the app can send mails on behalf of any user in your tenant. To limit the permissions we leverage exchange application access policies.
Connect to Exchange Online with the ExchangeOnlineManagement PowerShell module Connect-ExchangeOnline
While fine-tuning and adjusting applocker policies for co-managed Windows 10 clients I got really annoyed by special characters commonly used in the German/Swiss language. The Intune portal seemed to use different encoding and didn’t allow me to just copy/paste the currently deployed policy and extend it with a new rule. I needed to request the original file that was uploaded to the tenant in order to adjust the rule. Instead of just accepting this I decided that it is time for an easier approach which I will share with you.
The actual issue # After uploading the XML with the applocker policies (in my case EXE rules), special characters like ‘ö’ or ‘ü’ have a weird encoding displayed in the portal. The next person that wants to edit the policy needs to take care to fix the unrecognized characters otherwise the publisher rule won’t work anymore.
Top: Portal view of the special characters, bottom: original file.
Fixing things # Save file with UTF-8 encoding # First, make sure that you saved an uploaded the file with UTF-8 encoding as this introduces support for most character sets. You can do this by selecting the encoding box in the right bottom of Visual Studio Code:
When working with the Microsoft Graph API or introducing the API to colleagues I often get asked about the steps required to obtain an access token for the API with PowerShell. Out in the wild, I’ve spotted many different ways and lots of implementations still relying on the ADAL (Active Directory Authentication Library) despite the fact that this client library is superseded by MSAL (Microsoft Authentication Library). So let’s talk about acquiring access token “in stile” with the most simple method available.
Why do we need an access token? # When talking about the Microsoft Graph API an access token fulfills two roles, first: prove authentication (proof of identity) second prove authorization (permissions). Each request needs to submit a request-header that contains the access token.
For an API it’s crucial to validate the authentication and authorization for every request. Otherwise, requests could be made to resources the actor has no access to.
What’s inside the access token # You did probably stumble over the terms “bearer authentication” or “bearer token” these describe a mechanism within the OAuth 2.0 Authorization framework to authenticate requests with access tokens. Tokens are issued by the authorization server (Azure AD) and contain a server-generated string in the format of a JSON Web Token (JWT) with the following information (the list is not exhaustive and truncated to only contain the most interesting parts):
Too lazy to sign your PowerShell scripts? Yes of course it provides security benefits but performing the steps manually can be easily forgotten and re-signing needs to happen after every script change. Because I like CI/CD topics and have not found a solution on the internet I decided to build a solution based on Azure capabilities. Furthermore, I wanted a solution which does not require to hand out the code signing certificate to the respective script author which can be useful if you have a bunch of people writing PowerShell scripts.
From a personal perspective, I would also recommend signing scripts you hand over to customers to ensure the integrity of the scripts because as soon as the script gets changed the signature is invalid.
You can find more general recommendations about script signing in the PowerShell docs.
Solution overview # The key vault will store the code signing certificate with an access policy that allows access from the Azure DevOps pipeline.
The pipeline consists of the following steps:
Import code signing certificate The certificate is supplied as secret in a variable group which is linked to the key vault Access to the key vault is granted with a service connection (service principal) Sign PowerShell scripts which contain a “magic token” All *.ps1 within the attached repository will be enumerated To control which scripts will be signed only those with the “magic token” get processed The “magic token” gets removed before signing the script Publish signed PowerShell scripts as pipeline artifacts To make them available for download
While looking into the new Microsoft Defender Antivirus report available in MEM (Intune) I discovered some machines which did not report any recent Defender antimalware scans, despite configured via configuration profile. Of course, AV scans are kinda old-fashioned against rapidly evolving threats but a regular quick scan won’t hurt anyone. Instead of having a look at every single machine affected, I decided to try out the new proactive remediations feature which went globally available last week and let endpoint analytics do the detection and remediation work for me. As a reference, I used the Tutorial: Proactive remediations from Microsoft which covers the process quite well.
PowerShell scrips # For Endpoint analytics / Proactive remediations we need two PowerShell scripts. The first script is used as a detection script and determines whether remediation is necessary based on the exit code. Exit code 0 indicates a healthy status and exit code 1 indicates remediation necessary. Remediation occurs with a second PowerShell script.
To detect the most recent Defender scan I used the Windows Eventlog. Event ID’s are documented here.
Detection script # Remediation script # The remediation script is just about a one-liner to trigger a quick scan. You can extend this based on your requirements and respective to your Intune settings. E.g. triggering a signature update for a scan or adding additional steps.
You always wanted to automate a specific action within Intune / the Microsoft Endpoint Manager Portal (MEM) but were afraid of the complexity? The Microsoft Graph API docs deliver you more questions instead of answers? Automating tasks within the MEM portal could be very easy, couldn’t it? I promise it will be much simpler with this magician trick.
Microsoft Endpoint Manager Portal # The MEM Portal UI relies on the Microsoft Graph API. This means that the UI where you create new settings and policies and the Intune backend are encapsulated with different layers. Communication between the UI and the backend happens with the Microsoft Graph API. With the developer tools we can trace network traffic and discover the request URLs and request body payload which are required to interact with the API.
{: .align-center}
Example about how to capture URLs and build a PowerShell script # Original request body: