Category Archives: Powershell

Microsoft Teams permission auditing

I lied, not just Teams, also Sharepoint and Onedrive!

As I am asked often how to report on specific permissions granted to individual (groups) of (internal/external) users….and Microsoft doesn’t have a good built-in solution, nor does the community seem to yet….this something was just asking to be coded!

My TeamPermissions PowerShell module will do exactly the above, a full report in XLSX, CSV or HTML format that contains ALL unique permissions for a given Team, Sharepoint site or Onedrive location for all files, folders, lists, list items etc. Example:

It uses the safe Entra Delegated Permission Flow for authentication so your credentials/tokens stay with you, but this does mean you have to run it as a Sharepoint Administrator (or Global Admin), there is no support for MI/SPN runs yet but can be added easily if there is much demand.

Since it exports to Excel in append mode, you could run it for multiple (or all) team sites and use e.g. Pivots to view all permissions for a given user.

Do note that although some work has been done on performance, it does not scan multiple locations in parallel yet, this will be added in a future version.

Example:

Install-PSResource -Name TeamPermissions -Repository PSGallery

#then get xlsx/html reports for the INT-Finance Department Team:

Get-TeamPermissions -teamName "INT-Finance Department" -ExpandGroups -OutputFormat XLSX,HTML

#Or get all permission for a Sharepoint site:

Get-TeamPermissions -TeamSiteUrl "https://tenant.sharepoint.com/sites/site" -ExpandGroups -OutputFormat Default

Notes

Required PS modules: PnP.PowerShell, ImportExcel

Running multiple times will append data if you don’t move the (xlsx, csv, html) file, turning the report into a multi-location report.

Multi threading in ADDRS

I’ve added basic multi-threading to ADDRS: https://www.powershellgallery.com/packages/ADDRS/1.1.8

This solely ensures that the most compute intensive task (caching sizes/performance) is not repeated between jobs. You’ll still have to handle running multiple jobs using your own preferred method, e.g. foreach -parallel, runspaces or start-job. Example:


$scriptBlock = {
    Param(
        $vm,
        $measurePeriodHours,
        $workspace,
        $token
    )
    import-module ADDRS -force
    Login-AzAccount -AccessToken $token.Token -AccountId $token.UserId -Tenant $token.TenantId
    set-vmRightSize -doNotCheckForRecentResize -targetVMName $vm.Name -domain "lieben.nu" -measurePeriodHours $measurePeriodHours -workspaceId $workspace.CustomerId -Verbose -maintenanceWindowStartHour 22 -maintenanceWindowLengthInHours 3 -maintenanceWindowDay 6
}

Publishing an MSIX as CIM to AVD in a Pipeline

I wanted to put this out there as it felt like a nifty way to pipeline AVD MSIX files into AVD without any user interaction (other than a pipeline kicking off the script).

https://github.com/jflieben/assortedFunctionsV2/blob/main/publish-MSIXPackageToHostpool.ps1

Basically, above will grab the MSIX file from a known Azure Fileshare (after mounting). It’ll read the MSIX’s primary CIM file for meta data, use the Azure Rest API to add it to the hostpool and then updates a param file of an ARM template which can be used to e.g. update the appgroup in Azure.

You’ll need some background knowledge to re-use above in your specific situation 🙂

Code example:

#create the MSIX package object in the hostpool. Ensure the lastUpdated value is always unique otherwise it will fail to overwrite an existing package with the same value
$apiPostData = @{
    "properties" = @{
        "displayName" = if($packageMeta -match "(?<=<DisplayName>)(.*?)(?=<\/DisplayName>)"){$matches[1]}else{Throw "No display name found in AppManifest"}
        "imagePath" = $imagePath
        "isActive" = $True
        "isRegularRegistration" = $False
        "lastUpdated" = (get-itemproperty $packageFolder.FullName).LastWriteTimeUtc.AddSeconds((Get-Random -Minimum "-150" -Maximum 150)).ToString("yyyy-MM-ddThh:mm:ss")
        "packageApplications" = $packageApplications
        "packageDependencies" = @()
        "packageFamilyName" = "$($packageShortName)_$($packageFamily)"
        "packageName" = $packageShortName
        "packageRelativePath" = "\MSIXPackages\$($packageFolder.Name)"
        "version" = $packageVersion
    }
}

#send the actual API request to register the package in the hostpool using the pipeline serviceprincipal
try{
    $context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
    $token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://management.azure.com")          
    Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/$((get-azcontext).Subscription.id)/resourcegroups/rg-common-$($environment)-weeu-01/providers/Microsoft.DesktopVirtualization/hostPools/vdhp-common-$($environment)-weeu-01/msixPackages/$($packageFolder.Name)?api-version=2021-07-12" -Method PUT -UseBasicParsing -ContentType "application/json" -Body ($apiPostData | convertto-json -Depth 15) -Headers @{"Authorization"="Bearer $($token.AccessToken)"} -ErrorAction Stop
}catch{
    Write-Output $_
    closeCIMSession
    Throw
}

Guest report & cleanup new features

My script / runbook to automatically report on stale Guests and clean them up has received some updates

  • Exclusion Groups

Guests in Exclusion groups will never be deleted, even if they are in an Inclusion Group

  • Inclusion Groups

Guests in Inclusion Groups will be deleted if they meet the age requirements, all guests not in an inclusion group will be ignored

  • ReadOnly mode

Will pretend to delete guests are per configured settings, but won’t actually delete anything

download from git: https://gitlab.com/Lieben/assortedFunctions/-/blob/master/get-AzureAdInactiveGuestUsers.ps1?ref_type=heads

Map Onedrive profile Dir to O: at logon

For a basic scenario where users are unwilling or unable to navigate to the Onedrive folder in their profile, the following script will ensure all users on a given machine get their Onedrive profile folder mapped to the O: drive, it auto-installs as scheduled task at logon.

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/set-scheduledTaskToInstallODriveToOnedriveAtLogon.ps1