azureblog.co.uk
← cd ~/tools
// tools/scripts

Script library

All 44 scripts and queries from the posts on this site, in one place. Search by what you want to do, filter by language and copy. Each one links back to the post that explains it.

  1. PowerShell
    powershell
    Connect-MgGraph -Scopes "Directory.Read.All"
    
    Get-MgOauth2PermissionGrant -All |
      Where-Object { $_.Scope -match "Mail\.|Files\.|Sites\.|Directory\." } |
      ForEach-Object {
        [pscustomobject]@{
          App         = (Get-MgServicePrincipal -ServicePrincipalId $_.ClientId).DisplayName
          ConsentType = $_.ConsentType
          Scope       = $_.Scope
        }
      } | Sort-Object App | Format-Table -AutoSize
  2. powershell
    $cert = New-SelfSignedCertificate -Subject "CN=Graph-UserReport" `
      -CertStoreLocation "Cert:\LocalMachine\My" `
      -KeyExportPolicy NonExportable -KeySpec Signature `
      -NotAfter (Get-Date).AddYears(1)
    
    Export-Certificate -Cert $cert -FilePath C:\Temp\Graph-UserReport.cer
  3. powershell
    Connect-MgGraph -ClientId "<app-id>" -TenantId "<tenant-id>" `
      -CertificateThumbprint "<thumbprint>" -NoWelcome
    
    Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled |
      Export-Csv C:\Reports\users.csv -NoTypeInformation
  4. http
    PATCH https://graph.microsoft.com/beta/policies/authenticationmethodspolicy
    Content-Type: application/json
    
    {
      "optOutSettings": {
        "passkeyDynamicMigration": true
      }
    }
  5. shell
    az consumption budget create \
      --budget-name monthly-dev \
      --amount 500 \
      --category cost \
      --time-grain monthly \
      --start-date 2026-09-01 \
      --end-date 2027-08-31
  6. shell
    nslookup stdata.blob.core.windows.net
  7. powershell
    # PowerShell equivalent, showing the CNAME chain
    Resolve-DnsName stdata.blob.core.windows.net | Format-Table Name, Type, IPAddress, NameHost
  8. shell
    az keyvault update --name kv-app-prod --resource-group rg-app --enable-rbac-authorization true
  9. Finding vaults still on access policiesKey Vault: move from access policies to Azure RBAC
    KQL
    kql
    // Azure Resource Graph
    resources
    | where type == "microsoft.keyvault/vaults"
    | extend rbac = tobool(properties.enableRbacAuthorization)
    | where rbac != true
    | project name, resourceGroup, subscriptionId
  10. powershell
    Connect-MgGraph -Scopes "Device.Read.All"
    
    $cutoff = (Get-Date).AddDays(-90)
    Get-MgDevice -All -Property DisplayName,OperatingSystem,ApproximateLastSignInDateTime,AccountEnabled |
      Where-Object { $_.ApproximateLastSignInDateTime -lt $cutoff } |
      Sort-Object ApproximateLastSignInDateTime |
      Select-Object DisplayName, OperatingSystem, ApproximateLastSignInDateTime, AccountEnabled
  11. powershell
    Connect-MgGraph -Scopes "Device.ReadWrite.All"
    $cutoff = (Get-Date).AddDays(-90)
    $stale = Get-MgDevice -All -Property Id,DisplayName,ApproximateLastSignInDateTime,AccountEnabled |
      Where-Object { $_.AccountEnabled -and $_.ApproximateLastSignInDateTime -lt $cutoff }
    $stale | Export-Csv stale-devices.csv -NoTypeInformation   # keep a record
    $stale | ForEach-Object { Update-MgDevice -DeviceId $_.Id -AccountEnabled:$false }
  12. shell
    IntuneWinAppUtil.exe -c C:\Packages\7zip -s 7z-x64.msi -o C:\Packages\Output
  13. shell
    msiexec /i "7z-x64.msi" /qn /norestart
    msiexec /x {23170F69-40C1-2702-0000-000001000000} /qn /norestart
  14. PowerShell
    powershell
    $sizeGB = (Get-ChildItem "$env:SystemRoot\Temp" -Recurse -Force -ErrorAction SilentlyContinue |
      Measure-Object Length -Sum).Sum / 1GB
    if ($sizeGB -gt 5) { Write-Output "Temp is $([math]::Round($sizeGB,1)) GB"; exit 1 }
    Write-Output "Temp OK"; exit 0
  15. PowerShell
    powershell
    Get-ChildItem "$env:SystemRoot\Temp" -Recurse -Force -ErrorAction SilentlyContinue |
      Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-7) } |
      Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    Write-Output "Cleaned temp folder"; exit 0
  16. kql
    SigninLogs
    | where TimeGenerated > ago(7d) and ResultType != "0"
    | summarize Count = count() by ResultType, ResultDescription
    | top 20 by Count
  17. kql
    SigninLogs
    | where TimeGenerated > ago(3d)
    | where UserPrincipalName =~ "jane.doe@contoso.com"
    | project TimeGenerated, AppDisplayName, ResultType, ResultDescription,
              IPAddress, Location = tostring(LocationDetails.countryOrRegion),
              ConditionalAccessStatus
    | order by TimeGenerated desc
  18. KQL
    kql
    SigninLogs
    | where TimeGenerated > ago(1d) and ConditionalAccessStatus == "failure"
    | summarize Count = count() by UserPrincipalName, AppDisplayName
    | order by Count desc
  19. KQL
    kql
    let known = SigninLogs
    | where TimeGenerated between (ago(30d) .. ago(1d)) and ResultType == "0"
    | distinct UserPrincipalName, Country = tostring(LocationDetails.countryOrRegion);
    SigninLogs
    | where TimeGenerated > ago(1d) and ResultType == "0"
    | extend Country = tostring(LocationDetails.countryOrRegion)
    | join kind=leftanti known on UserPrincipalName, Country
    | project TimeGenerated, UserPrincipalName, Country, IPAddress, AppDisplayName
  20. kql
    SigninLogs
    | where TimeGenerated > ago(1h) and ResultType == "50126"
    | summarize Users = dcount(UserPrincipalName) by IPAddress
    | where Users > 10
    | order by Users desc
  21. kql
    SigninLogs
    | where TimeGenerated > ago(14d)
    | where ClientAppUsed !in ("Browser", "Mobile Apps and Desktop clients")
    | summarize Count = count() by ClientAppUsed, UserPrincipalName, AppDisplayName
    | order by Count desc
  22. kql
    AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(1d)
    | summarize Count = count(), Failures = countif(ResultType != "0") by AppDisplayName
    | order by Count desc
  23. 8. Service principal sign-ins from unexpected IPsSix KQL queries for Entra sign-in logs every admin should keep
    KQL
    kql
    AADServicePrincipalSignInLogs
    | where TimeGenerated > ago(7d) and ResultType == "0"
    | summarize IPs = make_set(IPAddress, 20), Count = count() by ServicePrincipalName
    | where array_length(IPs) > 3
    | order by Count desc
  24. shell
    az lock create --name do-not-delete \
      --lock-type CanNotDelete \
      --resource-group rg-core-networking \
      --notes "Hub networking. Raise a change before removing."
  25. shell
    # Every lock in the current subscription
    az lock list --output table
    
    # Remove one (needs Microsoft.Authorization/locks/delete)
    az lock delete --name do-not-delete --resource-group rg-core-networking
  26. powershell
    Connect-MgGraph -Scopes "Group.Read.All"
    
    Get-MgGroup -All -Filter "hasMembersWithLicenseErrors eq true" |
      Select-Object DisplayName, Id
  27. powershell
    Connect-MgGraph -Scopes "User.ReadWrite.All"
    $g = Get-MgGroup -Filter "displayName eq 'LIC-M365-E5'"
    Get-MgGroupMember -GroupId $g.Id -All | ForEach-Object {
      Invoke-MgLicenseUser -UserId $_.Id
    }
  28. CLI / shell
    shell
    # Assign Allowed locations at a management group, UK regions only
    az policy assignment create \
      --name allowed-locations \
      --display-name "Allowed locations: UK" \
      --scope "/providers/Microsoft.Management/managementGroups/mg-landingzones" \
      --policy "e56962a6-4747-49cd-b67b-bf8b01975c4c" \
      --params '{ "listOfAllowedLocations": { "value": ["uksouth","ukwest"] } }'
  29. shell
    # Run a compliance scan now rather than waiting
    az policy state trigger-scan --resource-group rg-app-prod
    
    # What's non-compliant at a management group?
    az policy state summarize --management-group mg-landingzones
  30. yaml
    permissions:
      id-token: write
      contents: read
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        environment: production
        steps:
          - uses: actions/checkout@v4
          - uses: azure/login@v2
            with:
              client-id: ${{ vars.AZURE_CLIENT_ID }}
              tenant-id: ${{ vars.AZURE_TENANT_ID }}
              subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
          - run: az group show --name rg-app
  31. powershell
    # Give a Function App's system-assigned identity read access to a storage account
    $principalId = az functionapp identity assign -g rg-app -n func-app --query principalId -o tsv
    az role assignment create --assignee $principalId \
      --role "Storage Blob Data Reader" \
      --scope "/subscriptions/<sub-id>/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stdata"
  32. kql
    SigninLogs
    | where TimeGenerated > ago(30d) and ResultType == "0"
    | summarize Users = dcount(UserPrincipalName), SignIns = count() by Country = tostring(LocationDetails.countryOrRegion)
    | order by SignIns desc
  33. CLI / shell
    shell
    az account set --subscription "<subscription-id>"
    az security pricing create --name CloudPosture --tier Standard
    az security pricing show --name CloudPosture
  34. kql
    // Azure Resource Graph: Defender CSPM status per subscription
    securityresources
    | where type == "microsoft.security/pricings" and name == "CloudPosture"
    | project subscriptionId, tier = tostring(properties.pricingTier)
    | order by tier asc
  35. kql
    SigninLogs
    | where TimeGenerated > ago(7d)
    | mv-expand ConditionalAccessPolicies
    | where ConditionalAccessPolicies.displayName == "Require compliant device"
    | where ConditionalAccessPolicies.result == "reportOnlyFailure"
    | summarize Failures = count() by UserPrincipalName, AppDisplayName
    | order by Failures desc
  36. kql
    SigninLogs
    | where UserPrincipalName in~ ("bg-admin1@contoso.onmicrosoft.com", "bg-admin2@contoso.onmicrosoft.com")
    | project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, ResultType
  37. PowerShell
    powershell
    Connect-MgGraph -Scopes "RoleEligibilitySchedule.Read.Directory",
                            "RoleAssignmentSchedule.ReadWrite.Directory"
    
    $me = Get-MgUser -UserId (Get-MgContext).Account
  38. powershell
    $eligible = Get-MgRoleManagementDirectoryRoleEligibilityScheduleInstance -Filter "principalId eq '$($me.Id)'" -ExpandProperty RoleDefinition
    
    $eligible | Select-Object @{n="Role";e={$_.RoleDefinition.DisplayName}}, DirectoryScopeId, EndDateTime
  39. powershell
    $role = $eligible | Where-Object { $_.RoleDefinition.DisplayName -eq "Exchange Administrator" }
    
    $params = @{
        Action           = "selfActivate"
        PrincipalId      = $me.Id
        RoleDefinitionId = $role.RoleDefinitionId
        DirectoryScopeId = $role.DirectoryScopeId
        Justification    = "Investigating mail flow issue"
        ScheduleInfo     = @{
            StartDateTime = Get-Date
            Expiration    = @{ Type = "AfterDuration"; Duration = "PT2H" }
        }
    }
    New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params
  40. Check the result, extend or end earlyActivate PIM roles from PowerShell with Microsoft Graph
    PowerShell
    powershell
    # Requests you've made, newest first
    Get-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -Filter "principalId eq '$($me.Id)'" |
      Sort-Object CreatedDateTime -Descending |
      Select-Object -First 5 Action, Status, RoleDefinitionId, CreatedDateTime
    
    # Finished early? Deactivate rather than leave it running
    $params.Action = "selfDeactivate"
    $params.Remove("ScheduleInfo"); $params.Remove("Justification")
    New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params
  41. powershell
    function Enable-PimRole {
        param([Parameter(Mandatory)][string]$Role,
              [string]$Reason = "Planned admin work",
              [int]$Hours = 1)
        $me = Get-MgUser -UserId (Get-MgContext).Account
        $r = Get-MgRoleManagementDirectoryRoleEligibilityScheduleInstance -Filter "principalId eq '$($me.Id)'" -ExpandProperty RoleDefinition |
             Where-Object { $_.RoleDefinition.DisplayName -eq $Role }
        if (-not $r) { throw "Not eligible for $Role" }
        New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter @{
            Action = "selfActivate"; PrincipalId = $me.Id
            RoleDefinitionId = $r.RoleDefinitionId; DirectoryScopeId = $r.DirectoryScopeId
            Justification = $Reason
            ScheduleInfo = @{ StartDateTime = Get-Date; Expiration = @{ Type = "AfterDuration"; Duration = "PT$($Hours)H" } }
        }
    }
    
    Enable-PimRole -Role "Intune Administrator" -Reason "Deploying compliance policy" -Hours 2
  42. powershell
    Connect-MgGraph -Scopes "Application.Read.All"
    
    $days = 60
    $now  = Get-Date
    
    Get-MgApplication -All -Property DisplayName,AppId,PasswordCredentials,KeyCredentials |
      ForEach-Object {
        $app = $_
        $creds = @(
          $app.PasswordCredentials | ForEach-Object { [pscustomobject]@{ Type="Secret";      Name=$_.DisplayName; End=$_.EndDateTime } }
          $app.KeyCredentials      | ForEach-Object { [pscustomobject]@{ Type="Certificate"; Name=$_.DisplayName; End=$_.EndDateTime } }
        )
        foreach ($c in $creds) {
          [pscustomobject]@{
            App      = $app.DisplayName
            AppId    = $app.AppId
            Type     = $c.Type
            Name     = $c.Name
            Expires  = $c.End
            DaysLeft = [int]($c.End - $now).TotalDays
          }
        }
      } |
      Where-Object DaysLeft -le $days |
      Sort-Object DaysLeft |
      Format-Table -AutoSize
  43. PowerShell
    powershell
    Connect-MgGraph -Scopes "Application.Read.All"
    
    Get-MgServicePrincipal -All -Property DisplayName,KeyCredentials,PreferredSingleSignOnMode |
      Where-Object PreferredSingleSignOnMode -eq "saml" |
      ForEach-Object {
        foreach ($k in $_.KeyCredentials | Where-Object Usage -eq "Verify") {
          [pscustomobject]@{
            App     = $_.DisplayName
            Expires = $k.EndDateTime
            DaysLeft = [int]($k.EndDateTime - (Get-Date)).TotalDays
          }
        }
      } | Sort-Object DaysLeft | Format-Table
  44. powershell
    Connect-MgGraph -Scopes "UserAuthenticationMethod.ReadWrite.All"
    
    $tap = New-MgUserAuthenticationTemporaryAccessPassMethod -UserId "new.starter@contoso.com" -BodyParameter @{
        lifetimeInMinutes = 60
        isUsableOnce      = $true
    }
    $tap.TemporaryAccessPass
Read the post before running anything in production. Scripts use example names like contoso.com and need the scopes or roles described in each post.