diff --git a/Azure/User-Management/Export-EntraActiveUsers.ps1 b/Azure/User-Management/Export-EntraActiveUsers.ps1 new file mode 100644 index 0000000..3b176e0 --- /dev/null +++ b/Azure/User-Management/Export-EntraActiveUsers.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Exports all active Entra ID users and their attributes to a CSV file. + +.DESCRIPTION + Connects to Microsoft Graph, retrieves all users where AccountEnabled is true, + and exports every available attribute to a timestamped CSV file in an \Exports subfolder. + +.PARAMETER ExportPath + The subfolder name where the CSV will be saved. Defaults to 'Exports'. + +.EXAMPLE + Export-EntraActiveUsers +#> +function Export-EntraActiveUsers { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [string]$ExportPathName = "Exports" + ) + + process { + Set-StrictMode -Version Latest + + # 1. Setup Paths and Directories + $BasePath = $PSScriptRoot + if ([string]::IsNullOrWhiteSpace($BasePath)) { + $BasePath = Get-Location + } + + $TargetDirectory = Join-Path -Path $BasePath -ChildPath $ExportPathName + $Timestamp = Get-Date -Format "yyyyMMdd-HHmm" + $FileName = "EntraID_ActiveUsers_$Timestamp.csv" + $FullFilePath = Join-Path -Path $TargetDirectory -ChildPath $FileName + + try { + Write-Verbose "Ensuring directory exists: $TargetDirectory" + if (-not (Test-Path -Path $TargetDirectory)) { + New-Item -Path $TargetDirectory -ItemType Directory -Force | Out-Null + } + + # 2. Check for Microsoft Graph Connection + Write-Verbose "Checking Microsoft Graph connection..." + $CurrentContext = Get-MgContext + if (-not $CurrentContext) { + throw "No active Microsoft Graph connection found. Please run 'Connect-MgGraph' first." + } + + # 3. Retrieve Active Users + # We filter for AccountEnabled eq true and select all properties (*) + Write-Verbose "Fetching active users from Entra ID..." + $UserFilter = "accountEnabled eq true" + + # Using -All to ensure we bypass the default page size limits + $Users = Get-MgUser -Filter $UserFilter -Property "*" -All -ErrorAction Stop + + if ($null -eq $Users -or $Users.Count -eq 0) { + Write-Warning "No active users found in the tenant." + return + } + + Write-Verbose "Found $($Users.Count) users. Exporting to $FullFilePath..." + + # 4. Export to CSV + $Users | Export-Csv -Path $FullFilePath -NoTypeInformation -Encoding utf8 + + Write-Output "Export successfully completed: $FullFilePath" + } + catch { + Write-Error "An error occurred during export: $($_.Exception.Message)" + } + } +} \ No newline at end of file diff --git a/Azure/User-Management/README.md b/Azure/User-Management/README.md new file mode 100644 index 0000000..555a20f --- /dev/null +++ b/Azure/User-Management/README.md @@ -0,0 +1,28 @@ +# User Management Modules +These script were written with the use of Gemini and are provided as is. Please test them before using anything in production + +## Prerequisites +For running the Modules you need the MSGraph Powershell module installed. Use the command below to install it if not already present on your system +``` +Install-Module -Name Microsoft.Graph +``` + +## Export Active user +Use the following commands to connect to the Graph APi and to export all active users including their attributes. +``` +# Authenticate +Connect-MgGraph -Scopes "User.Read.All" + +# Run the function with Verbose output to see progress +Export-EntraActiveUsers -Verbose +``` + +## Update Users +In order to update the users, change all attributes you want to change in the CSV and run the following commands. Change the path of the source file. +``` +# Authenticate +Connect-MgGraph -Scopes "User.ReadWrite.All" + +# Run the update with overwrite capability +Update-EntraUsersOverwrite -CsvPath "$PSScriptRoot\Exports\MasterUserList.csv" -Verbose +``` \ No newline at end of file diff --git a/Azure/User-Management/Update-EntraUsersDynamic.ps1 b/Azure/User-Management/Update-EntraUsersDynamic.ps1 new file mode 100644 index 0000000..08422c1 --- /dev/null +++ b/Azure/User-Management/Update-EntraUsersDynamic.ps1 @@ -0,0 +1,90 @@ +<# +.SYNOPSIS + Updates Entra ID users based on all available columns in a CSV file. + +.DESCRIPTION + Dynamically maps CSV headers to Microsoft Graph User properties. + Compares CSV values to live data and updates only when a difference is found. + +.PARAMETER CsvPath + The full path to the source CSV file. + +.EXAMPLE + Update-EntraUsersDynamic -CsvPath ".\Exports\UserUpdate_20260226.csv" +#> +function Update-EntraUsersDynamic { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path $_ })] + [string]$CsvPath + ) + + process { + Set-StrictMode -Version Latest + + # 1. Setup Logging + $LogPath = Join-Path -Path $PSScriptRoot -ChildPath "Logs" + if (-not (Test-Path $LogPath)) { New-Item -ItemType Directory -Force | Out-Null } + + $Timestamp = Get-Date -Format "yyyyMMdd-HHmm" + $LogFile = Join-Path -Path $LogPath -ChildPath "DynamicUpdate_$Timestamp.log" + + try { + # 2. Load Data and Headers + $UserData = Import-Csv -Path $CsvPath + $Columns = $UserData[0].PSObject.Properties.Name + + # Attributes we should NOT try to update + $ExcludedAttributes = @('UserPrincipalName', 'Id', 'DeletedDateTime', 'CreatedDateTime') + + if (-not (Get-MgContext)) { throw "Not connected to Microsoft Graph. Run Connect-MgGraph." } + + foreach ($Row in $UserData) { + $UPN = $Row.UserPrincipalName + if ([string]::IsNullOrWhiteSpace($UPN)) { continue } + + try { + # 3. Fetch current user with all properties defined in CSV headers + Write-Verbose "Processing $UPN..." + $CurrentTarget = Get-MgUser -UserId $UPN -Property $Columns -ErrorAction Stop + + $UpdateHash = @{} + + foreach ($Col in $Columns) { + # Skip excluded columns and empty CSV cells + if ($Col -in $ExcludedAttributes -or [string]::IsNullOrWhiteSpace($Row.$Col)) { + continue + } + + # 4. Compare CSV value to Current Entra ID value + $CsvValue = $Row.$Col + $CurrentValue = $CurrentTarget.$Col + + if ($CsvValue -ne $CurrentValue) { + $UpdateHash[$Col] = $CsvValue + } + } + + # 5. Execute Update if changes exist + if ($UpdateHash.Count -gt 0) { + Update-MgUser -UserId $UPN @UpdateHash -ErrorAction Stop + Write-Output "SUCCESS: Updated $UPN ($($UpdateHash.Keys -join ', '))" + "$Timestamp,$UPN,Updated,$($UpdateHash.Keys -join ';')" | Out-File $LogFile -Append + } + else { + Write-Verbose "SKIP: No changes for $UPN" + "$Timestamp,$UPN,NoChange,Matched" | Out-File $LogFile -Append + } + } + catch { + Write-Warning "ERROR: Could not update $UPN - $($_.Exception.Message)" + "$Timestamp,$UPN,Error,$($_.Exception.Message)" | Out-File $LogFile -Append + } + } + } + catch { + Write-Error "Fatal Script Error: $($_.Exception.Message)" + } + } +} \ No newline at end of file