Site icon Gioxx.org

PowerShell 7: disattivazione di un servizio Microsoft 365 via Graph

PowerShell: usare Try/Catch per un output pulito in caso di errore

È un argomento già discusso e sul quale sono già tornato ma che rimane interessante e “comodo da disturbare” in caso di necessità vista la velocità delle API Graph e la loro capacità di andare sempre molto nello specifico dell’esigenza. Il caso d’uso riguarda la rimozione di un singolo piano di una licenza (due) ben specifica di Microsoft 365, più precisamente sto parlando del piano legato a Skype for Business e la licenza di riferimento è la Microsoft 365 Business Standard (e la stessa cosa succede nella Microsoft 365 Business Premium).

In parte è prendere ciò che era stato fatto e riportato nell’articolo Licenze Microsoft 365: modifica bulk via PowerShell 7 e Graph ma con una piccola differenza: nel precedente caso mi serviva cambiare completamente licenza assegnata a un gruppo di utenti e – contestualmente – disattivare proprio il piano relativo a Skype for Business in un solo colpo. Qui si tratta di lasciare tutto invariato rispetto alla situazione che si incontra, ma andare a rimuovere il solo piano Skype for Business che è stato reso disponibile anche sulle licenze Microsoft 365 Business (Standard / Premium), vanificando così la disattivazione dello stesso piano sulle licenze di altro tipo.

Il ragionamento (e lo script) è adattabile a qualsiasi altro piano esistente, va da sé e non ci dovrebbe neanche essere necessità di dirlo.

Connettiti a Microsoft Graph. Se non sai come farlo, sbircia uno dei miei precedenti articoli.

Cerca (e rimuovi)

Ho voluto spezzare il processo in due.
Una prima parte si occuperà di cercare i piani attivi in mezzo alle licenze di tutti gli utenti del tenant a prescindere da quelle che essi hanno associate. Se un particolare piano è attivo io voglio sapere di quale si tratta nello specifico e da quale licenza proviene.

findSkypeActivePlans.ps1 si occuperà di fare la ricerca e salvare il risultato della medesima all’interno di un file CSV che verrà creato all’interno della cartella attiva di PowerShell, removeSkypeActivePlans.ps1 prenderà quel file CSV (che tu passerai da riga di comando) e andrà a rimuovere il piano desiderato e precedentemente individuato. Ho pubblicato entrambi gli script su Gist:

<#
.SYNOPSIS
Find active Skype for Business services on the entire tenant using Microsoft Graph.
.DESCRIPTION
Find active Skype for Business services on the entire tenant using Microsoft Graph. Generates an array containing the detected user and license and displays it on the screen.
It also saves the same data in a CSV file within the folder from which you are launching the script (Current Directory in PowerShell) that can be used for later manipulation of users and licenses (and active Skype for Business plans).
You can execute this script without parameters and wait for results.
.NOTES
Filename: findSkypeActivePlans.ps1
Version: 0.1, 2023
Author: GSolone
Blog: gioxx.org
Twitter: @gioxx
Changes:
13/9/23- Change: I take out currentSkuId and UserPrincipalName which are needed later to "feed" the Remove script.
8/9/23- First version of the script.
.COMPONENT
-
.LINK
https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/get-mgsubscribedsku?view=graph-powershell-1.0
https://learn.microsoft.com/it-it/microsoft-365/enterprise/view-licenses-and-services-with-microsoft-365-powershell?view=o365-worldwide
https://learn.microsoft.com/it-it/azure/active-directory/enterprise-users/licensing-service-plan-reference#service-skype-for-business
https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/get-mguserlicensedetail?view=graph-powershell-1.0
https://learn.microsoft.com/en-us/microsoft-365/enterprise/disable-access-to-services-with-microsoft-365-powershell?view=o365-worldwide
#>
function priv_SaveFileWithProgressiveNumber($path) {
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($path)
$extension = [System.IO.Path]::GetExtension($path)
$directory = [System.IO.Path]::GetDirectoryName($path)
$count = 1
while (Test-Path $path)
{
$fileName = $baseName + "_$count" + $extension
$path = Join-Path -Path $directory -ChildPath $fileName
$count++
}
return $path
}
Set-Variable ProgressPreference Continue
$Result = @()
$ProcessedCount = 0
# Check https://learn.microsoft.com/it-it/azure/active-directory/enterprise-users/licensing-service-plan-reference#service-skype-for-business
$skypePlans = @(
"afc06cb0-b4f4-4473-8286-d644f70d8faf",
"b2669e95-76ef-4e7e-a367-002f60a39f3e",
"0feaeb32-d00e-4d66-bd5a-43b5b83db82c",
"70710b6b-3ab4-4a38-9f6d-9f169461650a"
)
$skypePlansActive = 0
$skypePlansActiveUsers = @()
$Users = Get-MgUser -Filter 'assignedLicenses/$count ne 0' -ConsistencyLevel eventual -CountVariable totalUsers -All
$Users | ForEach {
$ProcessedCount++
$PercentComplete = (($ProcessedCount / $totalUsers) * 100)
$User = $_
Write-Progress -Activity "Processing $($User.DisplayName)" -Status "$ProcessedCount out of $totalUsers ($($PercentComplete.ToString('0.00'))%)" -PercentComplete $PercentComplete
$GraphLicense = Get-MgUserLicenseDetail -UserId $User.Id
if ( $GraphLicense -ne $null ) {
ForEach ( $License in $($GraphLicense.SkuPartNumber) ) {
$currentSkuId = $GraphLicense | ? { $_.SkuPartNumber -eq $license } | select -ExpandProperty SkuId
$servicePlans = $GraphLicense | ? { $_.SkuPartNumber -eq $license } | select -ExpandProperty ServicePlans
$mcoStatus = $servicePlans | ? { $_.ServicePlanId -in $skypePlans }
if ( $mcoStatus -ne $null -and $mcoStatus.ProvisioningStatus -eq "Success" ) {
$skypePlansActive++
$skypePlansActiveUsers += New-Object -TypeName PSObject -Property $([ordered]@{
DisplayName = $User.DisplayName
UserPrincipalName = $User.UserPrincipalName
SMTPAddress = $User.Mail
SkuId = $currentSkuId
SkypePlan = $mcoStatus.ProvisioningStatus
SkypePlanName = $mcoStatus.ServicePlanName
SkypePlanId = $mcoStatus.ServicePlanId
})
break
}
}
}
}
if ( $skypePlansActive -gt 0 ) {
$CSV = priv_SaveFileWithProgressiveNumber("$($PWD)\$((Get-Date -format "yyyyMMdd").ToString())_M365-Skype-Active-Users.csv")
Write-Host "`nSkype Active Plans found: $skypePlansActive`nAlso saved as $CSV" -f "Yellow"
$skypePlansActiveUsers | Select DisplayName, SMTPAddress, SkypePlanName | Out-Host
$skypePlansActiveUsers | Export-CSV $CSV -NoTypeInformation -Encoding UTF8 -Delimiter ";"
}
<#
.SYNOPSIS
Remove active Skype for Business services on the entire tenant using Microsoft Graph and a CSV file.
.DESCRIPTION
Remove active Skype for Business services on the entire tenant using Microsoft Graph (require findSkypeActivePlans.ps1 script).
You can run this script by passing the parameter for the CSV file to be used to remove the Skype plan found active.
.NOTES
Filename: removeSkypeActivePlans.ps1
Version: 0.1, 2023
Author: GSolone
Blog: gioxx.org
Twitter: @gioxx
Changes:
13/9/23- First version of the script.
.COMPONENT
-
.LINK
https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/get-mgsubscribedsku?view=graph-powershell-1.0
https://learn.microsoft.com/it-it/microsoft-365/enterprise/view-licenses-and-services-with-microsoft-365-powershell?view=o365-worldwide
https://learn.microsoft.com/it-it/azure/active-directory/enterprise-users/licensing-service-plan-reference#service-skype-for-business
https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/get-mguserlicensedetail?view=graph-powershell-1.0
https://learn.microsoft.com/en-us/microsoft-365/enterprise/disable-access-to-services-with-microsoft-365-powershell?view=o365-worldwide
#>
Param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, HelpMessage="CSV file to use (e.g. C:\Temp\20230908_M365-Skype-Active-Users.csv)")]
[string] $CSV
)
Set-Variable ProgressPreference Continue
$ProcessedCount = 0
$totalUsers = Import-Csv $CSV -Delimiter ";" | Measure-Object | Select-Object -ExpandProperty count
Write-Host "Rows: $($totalUsers)" -f "Yellow"
Import-Csv $CSV -Delimiter ";" | ForEach {
$ProcessedCount++
$PercentComplete = (($ProcessedCount / $totalUsers) * 100)
$User = $_
Write-Progress -Activity "Processing $($User.DisplayName) ($($User.SkypePlanName))" -Status "$ProcessedCount out of $totalUsers ($($PercentComplete.ToString('0.00'))%)" -PercentComplete $PercentComplete
$skypePlanToRemove = @(
@{
SkuId = $($User.SkuId)
DisabledPlans = $($User.SkypePlanId)
}
)
Set-MgUserLicense -UserId $User.UserPrincipalName -RemoveLicenses @() -AddLicenses $skypePlanToRemove | Out-Null
}

Quello che ho fatto è stato prendere in esame la solita documentazione Microsoft con tutte le corrispondenze tra nomi licenze / piani e relativo GUID (go.gioxx.org/m365-licenseguid), individuare quelli relativi a Skype for Business (afc06cb0-b4f4-4473-8286-d644f70d8faf, b2669e95-76ef-4e7e-a367-002f60a39f3e, 0feaeb32-d00e-4d66-bd5a-43b5b83db82c, 70710b6b-3ab4-4a38-9f6d-9f169461650a) e darli in pasto al ciclo di controllo.

Se anche solo uno di quei GUID vengono rilevati all’interno del più nutrito gruppo di GUID composto dai vari piani associati a una determinata licenza, ecco che allora raccolgo il risultato in un vettore portandomi dietro tra le altre cose anche il nome dell’utente, il suo indirizzo di posta elettronica, lo SkuId della licenza che contiene il piano “incriminato” e l’ID preciso del piano individuato. Questi due ultimi dati sono fondamentali per poter effettuare successivamente la rimozione del piano dalla licenza.

Rimozione sì, ma con “Add

Tranquillo, non ho bevuto, non ancora almeno.
Lo script di rimozione del piano (removeSkypeActivePlans.ps1) necessita di un solo parametro obbligatorio: il file CSV precedentemente prodotto dallo script di ricerca (findSkypeActivePlans.ps1) e dovrai passarglielo tu tramite riga di comando (usalo come primo parametro direttamente, altrimenti ti verrà richiesto comunque a video nel momento in cui lancerai lo script senza).

A quel punto inizierà a lanciare la disattivazione del piano andandolo a “spegnere” all’interno della licenza che lo contiene. Per farlo avevo stupidamente pensato che avrei dovuto effettuare un’operazione di Set-MgUserLicense con parametro RemoveLicenses, ma in realtà sbagliavo, perché avrei causato la rimozione completa della licenza padre (che contiene il piano “indesiderato“), ho quindi modificato lo script affinché non rimuova alcunché ma vada ad “aggiungere” la medesima licenza ma con un piano in meno, ed ecco spiegato il perché dell’istruzione finale Set-MgUserLicense -UserId $User.UserPrincipalName -RemoveLicenses @() -AddLicenses $skypePlanToRemove

Ho salvato entrambi gli script anche in un nuovo repository dedicato allo scripting che va oltre il modulo ToyBox che sto continuando a mantenere (abbandonando così la vecchia scuola degli script singoli, anche se devo decidermi a documentarlo completamente), mi servirà a mantenerli aggiornati più comodamente: github.com/gioxx/m365-tools/tree/main/removeSkypeActivePlans.

In caso di dubbi sai già cosa fare, l’area commenti è a tua totale disposizione :-)

#KeepItSimple

Correzioni, suggerimenti? Lascia un commento nell'apposita area qui di seguito o contattami privatamente.
Ti è piaciuto l'articolo? Offrimi un caffè! ☕ :-)

L'articolo potrebbe non essere aggiornato

Questo post è stato scritto più di 5 mesi fa, potrebbe non essere aggiornato. Per qualsiasi dubbio ti invito a lasciare un commento per chiedere ulteriori informazioni! :-)

Condividi l'articolo con i tuoi contatti:
Exit mobile version