-1

How would I speed up the processing of my PowerShell script?

At the moment it takes about 1-2 hours to run 1K+ uses records, really need to speed it up.

  # Must be run in Powershell 5.1 or else you can't get account creation date
    # Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

    $maximumfunctioncount = 8192

    Install-Module Microsoft.Graph -Scope CurrentUser -Force -AllowClobber

    Connect-MgGraph -Scopes "Directory.Read.All","User.Read.All","AuditLog.Read.All"

    Select-MgProfile -Name "Beta"

    # Add a line from the variable name onwards to uncomment the license SKU

    $LicenseSku = 'ENTERPRISEPACK'
   

    # Use this to check for the license SKU

    # Get-MgSubscribedSku -All -Select SkuPartNumber, SkuId, SkuPartNumber, ConsumedUnits | Format-List

 

    # Get the assigned users details & exports as a CSV file

    $licenseDetails = Get-MgSubscribedSku -All | Where SkuPartNumber -eq $LicenseSku

    Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($licenseDetails.SkuId) )" -ConsistencyLevel eventual -All | Export-Csv -Path "C:\temp\LiceneUsers.csv"

 

    # Imports the above CSV file

    $UserCSVExtract = Import-Csv -Path "C:\temp\LiceneUsers.csv"

 

    # This is the final CSV file that is created with the user UPN + last sign-in date + account creation date

    $FinalCSVName = 'c:\temp\License CSV '+$LicenseSku + '.csv'

 

    $Result=@()

    foreach($user in $UserCSVExtract)

    {

        $usersignindate = Get-MgUser -UserId $user.ID -Select SignInActivity | Select -ExpandProperty SignInActivity

       $userprops = [ordered]@{

            UserPrincipalName = $user.UserPrincipalName

            LastSignInDateTime = $usersignindate.LastSignInDateTime

            AccountCreationDate = $user.CreatedDateTime
    }

        $userObj =  new-object -Type PSObject -Property $userprops

        $Result += $userObj | Export-csv -Path $FinalCSVName -Append
    }
    $Result

EDIT2:

#Connect to Microsoft Graph
Connect-MgGraph -scope User.Read.All, AuditLog.read.All

#Select the beta profile
Select-MgProfile -name beta

#Gather all users in tenant
$AllUsers = Get-MgUser -All

#Create a new empty array list object
$Report = [System.Collections.Generic.List[Object]]::new()

Foreach ($user in $AllUsers)
{
    #Null variables
    $SignInActivity = $null
    $Licenses = $null

    #Display progress output
    Write-host "Gathering sign-in information for $($user.DisplayName)" -ForegroundColor Cyan

    #Get current user information
    $SignInActivitiy = Get-MgUser -UserId  $user.id -Property signinactivity | Select-Object -ExpandProperty signinactivity
    $licenses = (Get-MgUserLicenseDetail -UserId $User.id).SkuPartNumber -join ", "

    #Create informational object to add to report
    $obj = [pscustomobject][ordered]@{
            DisplayName              = $user.DisplayName
            UserPrincipalName        = $user.UserPrincipalName
            Licenses                 = $licenses
            LastInteractiveSignIn    = $SignInActivitiy.LastSignInDateTime
            LastNonInteractiveSignin = $SignInActivitiy.LastNonInteractiveSignInDateTime
        }
    
    #Add current user info to report
    $report.Add($obj)
}

$report | Export-CSV -path C:\temp\Microsoft365_User_Activity-Report.csv -NoTypeInformation
Arbelac
  • 1,698
  • 6
  • 37
  • 90
  • 2
    Have you not done any profiling, instrumentation, or investigation yet? Can you even tell us what part of the script is slow? Have you checked any error logs for performance warnings? – Dai May 08 '23 at 08:56
  • 2
    This `$Result += $userObj | Export-csv -Path $FinalCSVName -Append` statement violates two common PowerShell Performance pitfalls: [void using the increase assignment operator (`+=`) to create a collection](https://stackoverflow.com/a/60708579/1701026) and [PowerShell scripting performance considerations\Avoid wrapping cmdlet pipelines](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations#avoid-wrapping-cmdlet-pipelines) – iRon May 08 '23 at 09:55
  • 1
    Instead of putting your code in a comment, update your [question](https://stackoverflow.com/posts/76198974/edit) and (as @Dai suggested) further narrow down the performance issue: If it is due to slow Graph server (`Get-MgUser`) response, there is little to do at the client side other than hamering on the Graph server with multiple threads using [`Foreach-Object -Parallel`](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/foreach-object#-parallel)... – iRon May 08 '23 at 11:32

1 Answers1

0

You don't really need to use the Beta endpoint as signInActivity is in the v1.0 endpoint and also unless you have another need for it export the results of the first Get-MgUser to a CSV isn't required. The slowest part of you script would be the individual Get-MgUser for each user in the CSV that would create one request for every user which isn't need because you can get all the information you after from the first request. (Even if you where going to do this you would want to batch the Get-MgUser).

Eg a condensed version of what you trying to do can be done using a oneliner

Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($licenseDetails.SkuId) )" -ConsistencyLevel eventual -All -Property UserPrincipalName,CreatedDateTime,signInActivity | select UserPrincipalName,CreatedDateTime,@{name="LastSignInDateTime";expression={$_.signInActivity.lastSignInDateTime}} | export-csv -NoTypeInformation "c:\temp\urep.csv"

[edit]

#Connect to Microsoft Graph
Connect-MgGraph -scope User.Read.All, AuditLog.read.All

$skuList = @{}
Get-MgSubscribedSku -All -Select SkuPartNumber, SkuId, SkuPartNumber | ForEach-Object {$skuList.add($_.SkuId,$_.SkuPartNumber)}
#Gather all users in tenant
$AllUsers = Get-MgUser -All -Property DisplayName,UserPrincipalName,SigninActivity,AssignedLicenses

#Create a new empty array list object
$Report = [System.Collections.Generic.List[Object]]::new()

Foreach ($user in $AllUsers)
{
    #Null variables 
    $Licenses = @()

    #Display progress output
    Write-host "Gathering sign-in information for $($user.DisplayName)" -ForegroundColor Cyan

    foreach($license in $user.AssignedLicenses){
        if($skuList.ContainsKey($license.SkuId)){
            $Licenses += $skuList[$license.SkuId]
        }else{
            $Licenses += $license.SkuId
        }
    }   

    #Create informational object to add to report
    $obj = [pscustomobject][ordered]@{
            DisplayName              = $user.DisplayName
            UserPrincipalName        = $user.UserPrincipalName
            Licenses                 = ($Licenses -join ',')
            LastInteractiveSignIn    = $user.SigninActivity.LastSignInDateTime
            LastNonInteractiveSignin = $user.SigninActivity.LastNonInteractiveSignInDateTime
        }
    
    #Add current user info to report
    $report.Add($obj)
}

$report | Export-CSV -path C:\temp\Microsoft365_User_Activity-Report.csv -NoTypeInformation
Glen Scales
  • 20,495
  • 1
  • 20
  • 23
  • Why do that ? you don't need to be doing 1 request per user when you can just do this when you enumerate all the users with the filter. It will always be slow if you go this way, i can show you how to batch that in groups of 20 but that's still a slower method then just using the filter and getting it in one go. Why do you need to use the csv like that ? – Glen Scales May 09 '23 at 05:55
  • You won't get good performance if you use Get-MgUserLicenseDetail and Get-MgUser against ever user (if you had 10000 users it would be 20000 requests) and you don't need to do it as I've include an example above as to how i would suggest you do it. – Glen Scales May 10 '23 at 00:31