MSEndpointMgr / IntuneWin32App

Provides a set of functions to manage all aspects of Win32 apps in Microsoft Intune.
MIT License
351 stars 91 forks source link

Enhance Intune Win32 App Lifecycle Automation with Improved programmatic Auth and upload Retry Logic #162

Open tjgruber opened 5 months ago

tjgruber commented 5 months ago

Summary

Overview:

This pull request introduces several key changes aimed at enhancing the reliability, flexibility, and automation capabilities (e.g., GitHub Actions, etc.) of the Intune Win32 app lifecycle management using modern authentication (MSAL.PS is not recommended). The changes primarily address issues related to token handling, authentication flow, Azure Storage blob upload processes, and error handling. Designed to automate via Azure Service Principal auth.

Key Changes:

  1. Enhanced Token Handling and Authentication Flow:

    • Addition of ExpiresOn Property: The New-ClientCredentialsAccessToken function now calculates and adds an ExpiresOn property to the token object for easier expiration checks.
    • Improved Token Expiration Check: The Test-AccessToken function now uses the ExpiresOn property to determine token expiration, enhancing reliability.
    • Refinements in Connect-MSIntuneGraph: Improved error handling and dynamic installation of the MSAL.PS module if still required for other auth methods.
    • New-AuthenticationHeader: Updated to support tokens with the ExpiresOn property directly.
  2. Content-Type Header Addition:

    • Invoke-AzureStorageBlobUploadFinalize: Added a content-type header to the REST request to ensure correct handling of the request body during upload finalization.
  3. Fixing System.DateTime Error:

    • Addressed a specific error related to invalid System.DateTime usage in Azure blob upload processes.
  4. Retry Logic and SAS URI Renewal:

    • Retry Mechanism: Implemented a retry mechanism for chunk uploads and finalization steps, with a maximum of 5 attempts.
    • SAS URI Renewal Adjustments: Initially added but removed SAS URI renewal on the first retry, opting for straightforward retries instead, and maintaining renewal attempts if needed based on a timer.
  5. Module Configuration:

    • Removed MSAL.PS from the required modules to support environments where it is dynamically loaded.
  6. This PR also introduces retry logic to improve the robustness of the Add-IntuneWin32App function, in hopes to help mitigate some transient errors in the following potential areas: (see Issue #8)

    • Win32 App Creation
    • Content Version Creation
    • File Content Creation

Detailed Changes:

  1. Token Handling Enhancements:

    • New-ClientCredentialsAccessToken: Added to handle modern OAuth2.0 client credentials flow without using MSAL.PS which is not recommended to use anymore.
    • Test-AccessToken: Updated to utilize the ExpiresOn property.
    • New-AuthenticationHeader: Updated to handle the ExpiresOn property directly.
    • Connect-MSIntuneGraph: Improved integration with new token functions and error handling.
  2. Azure Storage Blob Upload:

    • Invoke-AzureStorageBlobUpload: Enhanced retry logic for chunk uploads and finalization.
    • Invoke-AzureStorageBlobUploadChunk: Added exception throwing to support retry logic.
    • Invoke-AzureStorageBlobUploadFinalize: Added exception throwing to support retry logic during finalization.
    • Invoke-AzureStorageBlobUploadRenew: Added loop to check the status of the SAS URI renewal process.
  3. Module Configuration:

    • IntuneWin32App.psd1: Removed MSAL.PS from the required modules to allow dynamic loading.
  4. Error Handling Improvements:

    • Improved error messages and handling in various functions to provide more informative feedback and enhance robustness.
  5. Add-IntuneWin32App: (see Issue #8) Adds retry logic to the following:

    • Win32 App Creation
    • Content Version Creation
    • File Content Creation

Impact:

These improvements are expected to significantly enhance the automation capabilities of the Intune Win32 app lifecycle management process. They address previous limitations and errors, making the system more robust against issues like authentication, throttling / rate limitations, blob upload issues, and some other minor fixes.

Testing:

I have tested this using a fully automated GitHub Actions Intune app management lifecycle workflow, and locally on up to date PS 7. If others can help test other scenarios I would appreciate the help.

Example of the fix working successfully during a GitHub Action (throttling / network / etc issue): image


Please review the changes and provide feedback. Thank you!

obuolinis commented 1 month ago

I believe the upload retry logic is a badly needed feature of IntuneWin32App since Intune gets unpredictable too often. Especially for automation scenarios. So good job on implementing that Tim.

I'm now testing specifically this part of the PR but all the uploads end up with:

WARNING: An error occurred while creating the Win32 application. Error message: Cannot convert the "9/27/2024 10:30:07 AM +00:00" value of type "System.DateTimeOffset" to type "System.DateTime".

As it turns out there's a problem with a cast in this line https://github.com/MSEndpointMgr/IntuneWin32App/blob/84bc50a61b206bd3482d4e90bb0d6200f3914345/Private/Invoke-AzureStorageBlobUpload.ps1#L58

Looking at the original it looks like you just lost a call to the UtcDateTime property :) https://github.com/MSEndpointMgr/IntuneWin32App/blob/21b0c09691d864f30a00d611f568a6fde10b7afb/Private/Invoke-AzureStorageBlobUpload.ps1#L58

And honestly I believe a cast to datetime is redundant here since $Global:AccessToken.ExpiresOn.UtcDateTime property is already a DateTime. We can just rewrite the line like this:

$TokenExpireMinutes = [System.Math]::Round(($Global:AccessToken.ExpiresOn.UtcDateTime - $UTCDateTime).TotalMinutes)
tjgruber commented 1 month ago

On my end, I got errors when using the original:

https://github.com/MSEndpointMgr/IntuneWin32App/blob/21b0c09691d864f30a00d611f568a6fde10b7afb/Private/Invoke-AzureStorageBlobUpload.ps1#L58

Once I removed .UtcDateTime, it worked without issue locally and in GitHub Actions. I'm strictly using it in an automation scenario there since the PR. I'm curious why that part doesn't work for you but does for me and in automation?

I'll test your below suggestion to see if that works for me as well:

$TokenExpireMinutes = [System.Math]::Round(($Global:AccessToken.ExpiresOn.UtcDateTime - $UTCDateTime).TotalMinutes)`
obuolinis commented 1 month ago

My end goal is Github Actions as well - I already have an instance of Intune App Factory running there. But I'm testing all the code changes in Windows Sandbox, that's where I got the error above.

Not sure why the statement behaves differently for us, but it could be due to different locales and date/time formats.

tjgruber commented 3 days ago

My end goal is Github Actions as well - I already have an instance of Intune App Factory running there. But I'm testing all the code changes in Windows Sandbox, that's where I got the error above.

Not sure why the statement behaves differently for us, but it could be due to different locales and date/time formats.

@obuolinis Are you able to test this latest change? It is working well on everything I can test it on. I'd appreciate it if you could try it when you have time.

tjgruber commented 3 days ago

Next up, I need to add retry logic to almost all calls to MS. It's actually fairly frequent that I get request timeouts, and simply waiting a few seconds fixes it.