Dark mode

Dark mode

There are 0 results matching

article card image dark article card image light

Published by · Jun 25, 2024 tools · 2 mins read

Introducing: macOS JAMF Offboarding Tool

Offboarding macOS Devices from JAMF in Bulk using the JAMF API with a bash script ...

See More
article card image dark article card image light

Published by · Jun 25, 2024 tools · 2 mins read

Introducing: Microsoft Cloud License Automation Tool - Part 1

Automating Microsoft Cloud License Assignment and Reporting with PowerShell and Slack for Enterprise Mobility and Security E3 ...

See More
article card image dark article card image light

Published by · Jun 18, 2024 tools · 2 mins read

Introducing: Configuration Manager Set Implicit Uninstall Flag Tool

Setting Configuration Manager ConfigMgr Implicit Uninstall Flag with PowerShell for Required Application Deployments ...

See More
article card image dark article card image light

Published by · Jun 11, 2024 configmgr · 2 mins read

Configuration Manager Next Maintenance Window SQL Function

Get Next Configuration Manager Maintenance Window from a Schedule Token with Offset Days using an SQL Function. ...

See More
article card image dark article card image light

Published by · Jun 3, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 3

Add, Remove, or Replace Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 28, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 2

Get and Report Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 22, 2024 tools · 1 mins read

Introducing: Windows User Rights Assignment Tool - Part 1

Get Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune Linux Onboarding Tool

Onboard Ubuntu Linux devices to Microsoft Intune using a bash script. Installs prerequisites and starts the user-driven enrollment. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune macOS Onboarding Tool

Onboard macOS devices to Microsoft Intune using a bash script that initiates the process. Optionally, the script converts mobile accounts, resets the FileVault key, and removes ...

See More
article card image dark article card image light

Published by · Jan 23, 2024 tools · 3 mins read

Introducing: Intune Device Renaming Tool

Rename Intune Devices by setting a Prefix or using a User Attribute as Prefix. Supports Windows, macOS, and Linux ...

See More
article card image dark article card image light

Published by · Dec 8, 2023 intune · 5 mins read

Intune Logs: A Deep Dive into Locations, Interpretation, and Configuration

A Comprehensive Guide to Locations, Interpretation, and Configuration of Intune Logs ...

See More
article card image dark article card image light

Published by · Aug 14, 2023 configmgr · 2 mins read

Configuration Manager Console Extension to show Device Collection Membership with Console Builder

Use the Configuration Manager Console Builder, to add Collection Membership View to the Device Node ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 3 mins read

Introducing: Configuration Manager SSRS Dashboards

A Configuration Manager Dashboards solution with Reports for Software Updates, Bitlocker and more ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 2 mins read

Introducing: PowerShell WMI Management Toolkit Module

Streamline your WMI Namespace, Class, and Instance Management with our PowerShell Module ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 1 mins read

Configuration Manager detailed, filterable Port Documentation

Configuration Manager detailed, filterable port documentation as an excel document ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 3 mins read

Configuration Manager PXE TFTP Window Size Bug

Configuration Manager TFTP Block Size and TFTP Window Size Correct Configuration ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 4 mins read

Introducing: Configuration Manager Client Cache Cleanup Tool

Cleaning the Configuration Manager Client Cache the Right Way with PowerShell and Configuration Baselines ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 2 mins read

Introducing: Windows Cache Cleanup Tool

Cleaning Windows and Configuration Manager Caches for Configuration Manager Build and Capture Task Sequence or Standalone Use ...

See More
article card image dark article card image light

Published by · Jun 17, 2023 tools · 1 mins read

Introducing: Windows Update Database Reinitialization Tool

Proactively repair corrupted Windows Update Database with Powershell and Configuration Manager ...

See More
article card image dark article card image light

Published by · Mar 31, 2023 tools · 3 mins read

Introducing: Configuration Manager SQL Products Reporting

A Complete SQL Products reporting solution using Configuration Manager ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 configmgr · 1 mins read

Application Detection Method using the Configuration Manager Application Version

Replace hardcoded application version in scripts, with the Configuration Manager Application Version ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 tools · 3 mins read

Introducing: Certificate Management Toolkit

Managing Certificates with Configuration Manager and PowerShell by using just the Public Key ...

See More
article card image dark article card image light

Published by · Jan 7, 2019 reports · 2 mins read

Configuration Manager Device Boundary and Network Information Report

List Device Boundaries and Network Information with Configuration Manager ...

See More
article card image dark article card image light

Published by · Sep 9, 1980 help · 5 mins read

MEM.Zone Blog Publishing Documentation

Publishing Documentation for MEM.Zone ...

See More

We couldn’t find anything related to

“SCCM”

BLOG / tools zone

Introducing: Microsoft Cloud License Automation Tool - Part 1

Published by Paul Vilcu · Jun 25, 2024 · 2 mins read
article card image dark article card image light

Quick Summary

Microsoft has been involved in cloud computing for many years, and its approach to cloud licenses has evolved over time, from its early adoption in 2014 with the introduction of the Cloud Solution Provider (CSP) program it represented a significant step in the new direction.

Today in 2024, we have a plethora of cloud licenses available with various different costs. Balancing the assignment and reporting of these licenses in order not to overpay despite not using them is a challenge which we try to address in this two part blog series.

Part 1 covers reporting and alerting for _Enterprise Mobility and Security E3 Licenses which will enable you supplement additional licenses before they run out.

This tool can be used as a standalone script or integrated into your Azure Automation or Azure Functions.

Prerequisites


Understanding SKUs

Each cloud license from Microsoft has an particular skuId, which can be retrieved Online or using PowerShell.

Online

Microsoft official page for all Product Names and Service Plans, use GUIDs which can lead to some confusion.
As a workaround you can use the CSV version in the Prerequisites section.

PowerShell

Install-Module -Name AzureAD
Connect-AzureAD

Get-AzureADSubscribedSku | Select-Object -Property 'SkuId', 'SkuPartNumber'
Notes

AzureAD PowerShell module is needed in order to be able to run the Get-AzureADSubscribedSku cmdlet.


Parameters

Tenant

Specify the tenant ID

ApplicationClientID & Secret

Specify the Application (Client) ID to use.
Specify the Application (Client) Secret to use.

GUID

Specify the skuID

MinimumAmount

Specify the minimum license threshold before a slack message will be posted.

SlackWebhookURL

Specify the URL of the slack channel to post on.


Preview

article card image microsoft-cloud-license-automation-tool.gif
Microsoft Automate Cloud License Tool

Code

  1<#
  2.SYNOPSIS
  3    Checks the license usage of the specified license.
  4.DESCRIPTION
  5    Checks the license usage of the specified license. If the available amount is under the specified MinimumLicenseThreshold it will send a message to a slack webhook.
  6.PARAMETER TenantID
  7    Specifies the tenant ID.
  8.PARAMETER ClientID
  9    Specifies the application ID.
 10.PARAMETER ClientSecret
 11    Specifies the application secret.
 12.PARAMETER SkuIDs
 13    Specifies the skuId (GUID) of the license you want to check. This parameter is a Array.
 14.PARAMETER MinimumLicenseThreshold
 15    Limit for the minimum value, below this amount it will send a message to slack.
 16.PARAMETER SlackWebhookURI
 17    On what slack channel must the message be posted on.
 18.EXAMPLE
 19    Get-MSCloudLicenseUsage.ps1 -TenantID $TenantID -ClientID $ClientID -ClientSecret $ClientSecret -skuIds $skuIds -minAmount $minAmount -slackWebhookURI $slackWebhookURI
 20.INPUTS
 21    None.
 22.OUTPUTS
 23    None.
 24.NOTES
 25    Created by Ferry Bodijn
 26.LINK
 27    https://MEMZ.one/Get-MSCloudLicenseUsage
 28.LINK
 29    https://MEMZ.one/Get-MSCloudLicenseUsage-CHANGELOG
 30.LINK
 31    https://MEMZ.one/Get-MSCloudLicenseUsage-GIT
 32.LINK
 33    https://MEM.Zone/ISSUES
 34.COMPONENT
 35    MSGraph
 36.FUNCTIONALITY
 37    Get Cloud License Usage.
 38#>
 39
 40## Set script requirements
 41#Requires -Version 5.1
 42
 43##*=============================================
 44##* VARIABLE DECLARATION
 45##*=============================================
 46#region VariableDeclaration
 47
 48## Get script parameters
 49[CmdletBinding(SupportsShouldProcess=$true, DefaultParameterSetName = 'Custom')]
 50Param (
 51    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the tenant ID', Position = 0)]
 52    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Enter the tenant ID', Position = 0)]
 53    [ValidateNotNullorEmpty()]
 54    [Alias('Tenant')]
 55    [string]$TenantID,
 56    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the Application (Client) ID to use.', Position = 1)]
 57    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the Application (Client) ID to use.', Position = 1)]
 58    [ValidateNotNullorEmpty()]
 59    [Alias('ApplicationClientID')]
 60    [string]$ClientID,
 61    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the Application (Client) Secret to use.', Position = 2)]
 62    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the Application (Client) Secret to use.', Position = 2)]
 63    [ValidateNotNullorEmpty()]
 64    [Alias('ApplicationClientSecret')]
 65    [string]$ClientSecret,
 66    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the skuID that can be found on the site: https://learn.microsoft.com/en-us/entra/identity/users/licensing-service-plan-reference.', Position = 2)]
 67    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the skuID that can be found on the site: https://learn.microsoft.com/en-us/entra/identity/users/licensing-service-plan-reference.', Position = 2)]
 68    [ValidateNotNullorEmpty()]
 69    [Alias('GUID')]
 70    [array]$SkuIds,
 71    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the minimum amount of licenses before a slack message will be posted.', Position = 2)]
 72    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the minimum amount of licenses before a slack message will be posted.', Position = 2)]
 73    [ValidateNotNullorEmpty()]
 74    [Alias('MinimumAmount')]
 75    [int]$MinimumLicenseThreshold,
 76    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the URL of the slack channel to post on.', Position = 2)]
 77    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the URL of the slack channel to post on.', Position = 2)]
 78    [ValidateNotNullorEmpty()]
 79    [Alias('SlackWebhookURL')]
 80    [string]$SlackWebhookURI
 81)
 82
 83## Set log name and path
 84[string]$ScriptName = 'Get-MSCloudLicense'
 85[string]$ScriptPath = "$ENV:ProgramData"
 86
 87## SkuID examples
 88#SkuId: efccb6f7-5641-4e0e-bd10-b4976e1bf68e  - Enterprise Mobility + Security E3 license
 89#SkuId: 6a0f6da5-0b87-4190-a6ae-9bb5a2b9546a  - Windows 10/11 Enterprise E3
 90
 91#endregion VariableDeclaration
 92##*=============================================
 93##* END VARIABLE DECLARATION
 94##*=============================================
 95
 96##*=============================================
 97##* FUNCTION LISTINGS
 98##*=============================================
 99#region FunctionListings
100
101#region Function Write-Log
102Function Write-Log {
103<#
104.SYNOPSIS
105    Creates a Log entry and appends it to a Log file.
106.DESCRIPTION
107    Creates a Log entry and appends it to a Log file.
108.PARAMETER Severity
109    Specifies the message severity of (Informational, Success or Error).
110    Default is: 1.
111.PARAMETER Message
112    Specifies the log message to append.
113.EXAMPLE
114    Write-Log -Message 'Installation successful.' -Severity 2
115.EXAMPLE
116    Write-Log -Message 'Installation failed!' -Severity 3
117.INPUTS
118    System.Int32Type
119    System.String
120.OUTPUTS
121    .None
122.LINK
123    https://MEM.Zone
124.COMPONENT
125    Get-MSCloudLicense
126.FUNCTIONALITY
127    Creates a Log file entry
128#>
129    Param (
130        [Parameter(Mandatory = $false)]
131        [string]$Message,
132        [int]$Severity = 1
133    )
134    [string]$DeviceName = $env:COMPUTERNAME
135    $Time = Get-Date -Format 'HH:mm:ss'
136    $Date = Get-Date -Format 'yyyy-mm-dd'
137    [string]$FilePath = -join ($ScriptPath,$ScriptName.tx)
138    Switch ($Severity)
139    {
140        1 { $MessageSeverity = "INFORMATIONAL" }
141        2 { $MessageSeverity = "SUCCESS" }
142        3 { $MessageSeverity = "ERROR" }
143
144    }
145    $LogMessage = 'Device: {0}, Severity: {1}, Date: {2}, Time: {3}, Message: {4}`n' -f $DeviceName, $MessageSeverity, $Date, $Time, $Message
146    $LogMessage | Out-File -Append -Encoding 'UTF8' -FilePath $FilePath -Force
147}
148#endregion Function Write-Log
149
150#region Function Get-MSGraphAPIAccessToken
151Function Get-MSGraphAPIAccessToken {
152<#
153.SYNOPSIS
154    Gets a Microsoft Graph API access token.
155.DESCRIPTION
156    Gets a Microsoft Graph API access token, by using an application registered in EntraID.
157.PARAMETER TenantID
158    Specifies the tenant ID.
159.PARAMETER ClientID
160    Specify the Application Client ID to use.
161.PARAMETER Secret
162    Specify the Application Client Secret to use.
163.PARAMETER Scope
164    Specify the scope to use.
165    Default is: 'https://graph.microsoft.com/.default'.
166.PARAMETER GrantType
167    Specify the grant type to use.
168    Default is: 'client_credentials'.
169.EXAMPLE
170    Get-MSGraphAPIAccessToken -TenantID $TenantID -ClientID $ClientID -Secret $Secret -Scope 'https://graph.microsoft.com/.default' -GrantType 'client_credentials'
171.EXAMPLE
172    Get-MSGraphAPIAccessToken -TenantID $TenantID -ClientID $ClientID -Secret $Secret
173.INPUTS
174    None.
175.OUTPUTS
176    System.String
177.NOTES
178    Created by Ioan Popovici
179    v1.0.0 - 2024-01-11
180.LINK
181    https://MEMZ.one/Invoke-MSGraphAPI
182.LINK
183    https://MEMZ.one/Invoke-MSGraphAPI-CHANGELOG
184.LINK
185    https://MEMZ.one/Invoke-MSGraphAPI-GIT
186.LINK
187    https://MEM.Zone/ISSUES
188.COMPONENT
189    MSGraph
190.FUNCTIONALITY
191    Gets a Microsoft Graph API Access Token.
192#>
193[CmdletBinding()]
194    Param (
195        [Parameter(Mandatory = $true, HelpMessage = 'Specify the tenant ID.', Position = 0)]
196        [ValidateNotNullorEmpty()]
197        [Alias('Tenant')]
198        [string]$TenantID,
199        [Parameter(Mandatory = $true, HelpMessage = 'Specify the Application (Client) ID to use.', Position = 1)]
200        [ValidateNotNullorEmpty()]
201        [Alias('ApplicationClientID')]
202        [string]$ClientID,
203        [Parameter(Mandatory = $true, HelpMessage = 'Specify the Application (Client) Secret to use.', Position = 2)]
204        [ValidateNotNullorEmpty()]
205        [Alias('ApplicationClientSecret')]
206        [string]$ClientSecret,
207        [Parameter(Mandatory = $false, HelpMessage = 'Specify the scope to use.', Position = 3)]
208        [ValidateNotNullorEmpty()]
209        [Alias('GrantScope')]
210        [string]$Scope = 'https://graph.microsoft.com/.default',
211        [Parameter(Mandatory = $false, HelpMessage = 'Specify the grant type to use.', Position = 4)]
212        [ValidateNotNullorEmpty()]
213        [Alias('AccessType')]
214        [string]$GrantType = 'client_credentials'
215    )
216
217    Begin {
218
219        ## Assemble the token body for the API call. You can store the secrets in Azure Key Vault and retrieve them from there.
220        [hashtable]$Body = @{
221            client_id     = $ClientID
222            scope         = $Scope
223            client_secret = $ClientSecret
224            grant_type    = $GrantType
225        }
226
227        ## Assembly the URI for the API call
228        [string]$Uri = -join ('https://login.microsoftonline.com/', $TenantID, '/oauth2/v2.0/token')
229
230        ## Write Debug information
231        Write-Debug -Message "Uri: $Uri"
232        Write-Debug -Message "Body: $($Body | Out-String)"
233    }
234    Process {
235        Try {
236
237            ## Get the access token
238            $Response = Invoke-WebRequest -Method 'Post' -Uri $Uri -ContentType 'application/x-www-form-urlencoded' -Body $Body -UseBasicParsing
239
240            ## Assemble output object
241            $Output = [pscustomobject]@{
242                access_token = $Response.access_token
243                expires_in   = $Response.expires_in
244                granted_on   = $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
245            }
246        }
247        Catch {
248
249            ## Write exception to log.
250            #  Note that value__ is not a typo.
251            [string]$StatusCode = $PsItem.Exception.Response.StatusCode.value__
252            [string]$StatusDescription = $PSItem.Exception.Response.StatusDescription
253            #  Assemble the error message
254            [string]$Message = "Error getting MSGraph API Access Token for TenantID '{0}' with ClientID '{1}'.`n Status code {'2'}, Description {'3'}.`n{4}" -f $TenantID, $ClientID, $StatusCode, $StatusDescription, $PSItem.Exception.Message
255            Write-Log -Message $Message -Severity 3
256        }
257        Finally {
258            Write-Output -InputObject $Output
259        }
260    }
261    End {
262    }
263}
264#endregion
265
266#region Function Invoke-MSGraphAPI
267Function Invoke-MSGraphAPI {
268<#
269.SYNOPSIS
270    Invokes the Microsoft Graph API.
271.DESCRIPTION
272    Invokes the Microsoft Graph API with paging support.
273.PARAMETER Method
274    Specify the method to use.
275    Available options are 'GET', 'POST', 'PATCH', 'PUT' and 'DELETE'.
276    Default is: 'GET'.
277.PARAMETER Token
278    Specify the access token to use.
279.PARAMETER Version
280    Specify the version of the Microsoft Graph API to use.
281    Available options are 'Beta' and 'v1.0'.
282    Default is: 'Beta'.
283.PARAMETER Resource
284    Specify the resource to query.
285    Default is: 'deviceManagement/managedDevices'.
286.PARAMETER Parameter
287    Specify the parameter to use. Make sure to use the correct syntax and escape special characters with a backtick.
288    Default is: $null.
289.PARAMETER Body
290    Specify the request body to use.
291    Default is: $null.
292.PARAMETER ContentType
293    Specify the content type to use.
294    Default is: 'application/json'.
295.EXAMPLE
296    Invoke-MSGraphAPI -Method 'GET' -Token $Token -Version 'Beta' -Resource 'deviceManagement/managedDevices' -Parameter "filter=operatingSystem like 'Windows' and deviceName like 'MEM-Zone-PC'"
297.EXAMPLE
298    Invoke-MSGraphAPI -Token $Token -Resource 'users'
299.INPUTS
300    None.
301.OUTPUTS
302    System.Object
303.NOTES
304    Created by Ioan Popovici
305    v1.0.0 - 2024-01-11
306.LINK
307    https://MEMZ.one/Invoke-MSGraphAPI
308.LINK
309    https://MEMZ.one/Invoke-MSGraphAPI-CHANGELOG
310.LINK
311    https://MEMZ.one/Invoke-MSGraphAPI-GIT
312.LINK
313    https://MEM.Zone/ISSUES
314.COMPONENT
315    MSGraph
316.FUNCTIONALITY
317    Invokes the Microsoft Graph API.
318#>
319    [CmdletBinding()]
320    Param (
321        [Parameter(Mandatory = $false, HelpMessage = 'Specify the method to use.', Position = 0)]
322        [ValidateSet('GET', 'POST', 'PATCH', 'PUT', 'DELETE')]
323        [Alias('HTTPMethod')]
324        [string]$Method = 'GET',
325        [Parameter(Mandatory = $true, HelpMessage = 'Specify the access token to use.', Position = 1)]
326        [ValidateNotNullorEmpty()]
327        [Alias('AccessToken')]
328        [string]$Token,
329        [Parameter(Mandatory = $false, HelpMessage = 'Specify the version of the Microsoft Graph API to use.', Position = 2)]
330        [ValidateSet('Beta', 'v1.0')]
331        [Alias('GraphVersion')]
332        [string]$Version = 'Beta',
333        [Parameter(Mandatory = $true, HelpMessage = 'Specify the resource to query.', Position = 3)]
334        [ValidateNotNullorEmpty()]
335        [Alias('APIResource')]
336        [string]$Resource,
337        [Parameter(Mandatory = $false, HelpMessage = 'Specify the parameters to use.', Position = 4)]
338        [ValidateNotNullorEmpty()]
339        [Alias('QueryParameter')]
340        [string]$Parameter,
341        [Parameter(Mandatory = $false, HelpMessage = 'Specify the request body to use.', Position = 5)]
342        [ValidateNotNullorEmpty()]
343        [Alias('RequestBody')]
344        [string]$Body,
345        [Parameter(Mandatory = $false, HelpMessage = 'Specify the content type to use.', Position = 6)]
346        [ValidateNotNullorEmpty()]
347        [Alias('Type')]
348        [string]$ContentType = 'application/json'
349    )
350
351    Begin {
352
353        ## Assemble the URI for the API call
354        [string]$Uri = "https://graph.microsoft.com/$Version/$Resource"
355        If (-not [string]::IsNullOrWhiteSpace($Parameter)) { $Uri += "`?`$$Parameter" }
356
357        ## Assembly parameters for the API call
358        [hashtable]$Parameters = @{
359            'Uri'         = $Uri
360            'Method'      = $Method
361            'Headers'     = @{
362                'Content-Type'  = 'application\json'
363                'Authorization' = "Bearer $Token"
364            }
365            'ContentType' = $ContentType
366        }
367        If (-not [string]::IsNullOrWhiteSpace($Body)) { $Parameters.Add('Body', $Body) }
368
369        ## Write Debug information
370        Write-Debug -Message "Uri: $Uri"
371    }
372    Process {
373        Try {
374
375            ## Invoke the MSGraph API
376            $Output = Invoke-RestMethod @Parameters
377
378            ## If there are more than 1000 rows, use paging. Only for GET method.
379            If (-not [string]::IsNullOrEmpty($Output.'@odata.nextLink')) {
380                #  Assign the nextLink to the Uri
381                $Parameters.Uri = $Output.'@odata.nextLink'
382                [array]$Output += Do {
383                    #  Invoke the MSGraph API
384                    $OutputPage = Invoke-RestMethod @Parameters
385                    #  Assign the nextLink to the Uri
386                    $Parameters.Uri = $OutputPage.'@odata.nextLink'
387                    #  Write Debug information
388                    Write-Debug -Message "Parameters:`n$($Parameters | Out-String)"
389                    #  Return the OutputPage
390                    $OutputPage
391                }
392                Until ([string]::IsNullOrEmpty($OutputPage.'@odata.nextLink'))
393            }
394            Write-Verbose -Message "Got '$($Output.Count)' Output pages."
395        }
396        Catch {
397            [string]$Message = "Error invoking MSGraph API version '{0}' for resource '{1}' using '{2}' method.`n{3}" -f $Version, $Resource, $Method, $Error[0].Exception.Message
398            Write-Log -Message $Message -Severity 3
399            Write-Error -Message $Message
400        }
401        Finally {
402            $Output = If ($Output.value) { $Output.value } Else { $Output }
403            Write-Output -InputObject $Output
404        }
405    }
406    End {
407    }
408}
409#endregion Function Invoke-MSGraphAPI
410
411#region function Send-SlackMessage
412Function Send-SlackMessage {
413
414    [CmdletBinding()]
415    Param (
416        [Parameter(Mandatory = $true, HelpMessage = 'Specify the header.', Position = 0)]
417        [Alias('Title')]
418        [string]$Header,
419        [Parameter(Mandatory = $true, HelpMessage = 'Total licenses.', Position = 1)]
420        [Alias('Total')]
421        [string]$AvailableLicenses,
422        [Parameter(Mandatory = $true, HelpMessage = 'Used licenses.', Position = 2)]
423        [Alias('Used')]
424        [string]$UsedLicenses,
425        [Parameter(Mandatory = $true, HelpMessage = 'Licenses remaining.', Position = 3)]
426        [Alias('Remaining')]
427        [string]$RemainingLicenses,
428        [Parameter(Mandatory = $true, HelpMessage = 'Slack webhook URL', Position = 4)]
429        [Alias('WebhookURI')]
430        [string]$slackWebhookURI
431    )
432
433    ## Assemble the notification payload
434    [string]$Body =
435@"
436{
437    "blocks": [
438        {
439            "type": "header",
440            "text": {
441                "type": "plain_text",
442                "text": ":alert: $Header :alert:",
443                "emoji": true
444            }
445        },
446        {
447            "type": "divider"
448        },
449        {
450            "type": "section",
451            "text": {
452                "type": "mrkdwn",
453                "text": "*Available Licenses:* $AvailableLicenses"
454            }
455        },
456        {
457            "type": "section",
458            "text": {
459                "type": "mrkdwn",
460                "text": "*Used Licenses:* $UsedLicenses"
461            }
462        },
463        {
464            "type": "section",
465            "text": {
466                "type": "mrkdwn",
467                "text": "*Remaining Licenses: $RemainingLicenses*"
468            }
469        },
470        {
471            "type": "section",
472            "text": {
473                "type": "plain_text",
474                "text": "Please check if more licenses are required!",
475                "emoji": true
476            }
477        }
478    ]
479}
480"@
481
482    ## Post to slack
483    Start-Sleep -Seconds 1
484    Try {
485        $SlackNotify = Invoke-RestMethod -uri $SlackWebhookURI -Method 'POST' -Body $Body -ContentType 'application/json'
486
487        If ($SlackNotify -ne 'ok') { $Output = "Could not send Slack message! '$SlackNotify'" } Else { $Output = 'Slack Message Sent!' }
488    }
489    Catch {
490        Write-Error -Message "Error Sending Slack Message. $($_.Exception.Message)"
491    }
492    Finally {
493        Write-Output -InputObject $Output
494    }
495}
496#endregion Function Send-SlackMessage
497
498#endregion FunctionListings
499##*=============================================
500##* END FUNCTION LISTINGS
501##*=============================================
502
503##*=============================================
504##* SCRIPT BODY
505##*=============================================
506#region ScriptBody
507
508#First remove old log:
509If (Test-Path "$ScriptPath\$ScriptName.txt" -PathType 'Leaf') { Remove-Item -Path "$ScriptPath\$ScriptName.txt" -Force -Confirm:$false -ErrorAction 'SilentlyContinue' }
510
511## Write Start verbose message
512Write-Log -Message "Start '$ScriptName'" -Severity 1
513
514## Get API Token
515$AccessToken = Get-MSGraphAPIAccessToken -TenantID $TenantID ClientID $ClientID ClientSecret $ClientSecret -ErrorAction 'Stop'
516$Token = $AccessToken.access_token
517
518
519## Get all licenses from the tenant
520$AvailableLicenses = Invoke-MSGraphAPI -Method 'GET' -Version 'v1.0' -Token $Token -Resource 'subscribedSkus' -ErrorAction 'Stop'
521
522## Get information from 'https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/licensing-service-plan-reference'
523Try {
524
525    #  Fetch the content as bytes
526    $Response = Invoke-WebRequest -Uri "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv" -Method 'GET' -ErrorAction 'Stop'
527
528    #  Convert the content from a byte array to a string, assuming it's UTF8 encoded
529    $Utf8NoBom = New-Object 'System.Text.UTF8Encoding' $false
530    [string]$TranslationTable = $Utf8NoBom.GetString($Response.Content) | ConvertFrom-Csv
531}
532Catch {
533    $Output = "Could not get the translation table!"
534    Write-Log -Message $Output -Severity 3
535    Write-Verbose -Message $Output -Verbose
536}
537
538## Get each license information with the SkuID and check if it is lower then the specific value in $minAmount
539Foreach ($SkuID in $SkuIDs) {
540
541    ## Write verbose message
542    Write-Verbose = "Checking license: {0}" -f $SkuID
543
544    ## Calculate Token Expiry time
545    $TokenExpiryTime = $AccessToken.granted_on.ToUniversalTime().AddSeconds($AccessToken.expires_in)
546
547    ## If token expires in 5 minutes then generate new token
548    If ($TokenExpiryTime.AddMinutes(-5) -lt [DateTime]::UtcNow) {
549
550        ## Regenerate token
551        $AccessToken = Get-MSGraphAPIAccessToken -TenantID $TenantID ClientID $ClientID ClientSecret $ClientSecret -ErrorAction 'Stop'
552        $Token = $AccessToken.access_token
553    }
554
555    $SkuIdLicense = $AvailableLicenses | Where-Object { $PsItem.skuId -eq $SkuId }
556    $ResolvedSkuName = ($TranslationTable | Where-Object { $PSItem.GUID -eq $SkuID_license.skuId } | Sort-Object -Property 'Product_Display_Name' -Unique).Product_Display_Name
557
558    [int]$AvailableLicenses = $SkuIDLicense.prepaidUnits.enabled
559    [int]$UsedLicenses = $SkuIDLicense.consumedUnits
560    [int]$RemainingLicenses = $AvailableLicenses - $UsedLicenses
561
562    If ($RemainingLicenses -lt $MinimumLicenseThreshold) {
563        [string]$Header = "License: $ResolvedSkuName"
564        Send-SlackMessage -Header $Header -AvailableLicenses $AvailableLicenses -UsedLicenses $UsedLicenses -RemainingLicenses $RemainingLicenses -SlackWebhookURI $SlackWebhookURI
565        $Output = "The license: {0} is below the amount of: '{1}'. Message to slack has been send." -f $ResolvedSkuName, $MinimumLicenseThreshold
566        Write-Log -Message $Output -Severity 2
567    }
568    Else {
569        $Output = "There are {0} {1} licenses left. So it is not below: '{2}'." -f $RemainingLicenses, $ResolvedSkuName, $MinimumLicenseThreshold
570        Write-Log -Message $Output -Severity 1
571    }
572    Write-Verbose -Message $Output -Verbose
573}
574#endregion ScriptBody
575##*=============================================
576##* END SCRIPT BODY
577##*=============================================

SHARE

article card image dark article card image light

Published by · Jun 18, 2023 tools · 2 mins read

Introducing: Windows Cache Cleanup Tool

Cleaning Windows and Configuration Manager Caches for Configuration Manager Build and Capture Task Sequence or Standalone Use ...

See More
article card image dark article card image light

Published by · Jun 17, 2023 tools · 1 mins read

Introducing: Windows Update Database Reinitialization Tool

Proactively repair corrupted Windows Update Database with Powershell and Configuration Manager ...

See More