#V1.1 - 20200414 - REC - Fixed case issue with "resourcegroup" type. MS made changes. Had to correct my script to ignore case. #V1.2 - 20200506 - REC - Replaced comas witj semicolon in SubscriptionDescription #V2.0 - 20200918 - REC - Audit all resources #V2.1 - 20210527 - REC - Converting to PAIR extract and drop \\AZUWVGISPAIRP02\PairInputFiles\AzureDownload #V2.2 - 20210709 - REC - Filtering "Unknown" lines from exemption files. They will only appear in the log. Any new OdataTypes that appear will still show in the excemption file. #V2.3 - 20210715 - REC - Adding "Unknown" back in #V2.4 - 20220328 - REC - Resolving issue with CR/LF on Group Comments #V2.5 - 20240228 - Migrate to AZ commands #V2.6 - 20240626 - REC - Search and replace CR/LF within details scraped from AD #V2.7 - 20241021 - REC - Added "Connect-AzAccount -Credential $AZCred -Subscription 391e38fd-83c2-4f86-9b88-9513ed4f1add" $TenantId = '5d3e2773-e07f-4432-a630-1a0f68a28a05' $outfile10 = $PSScriptRoot + "\ProcessLog.txt" $OutLine='Job started at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline > $outfile10 $date = Get-Date -Format yyyy-MM-dd $CurrentDateAndTime = (Get-Date).ToString('yyyyMMddhhmmss') #timestamp #Install-WindowsFeature RSAT Import-Module ActiveDirectory Import-Module -Name Az Import-Module -Name AzureAD $OutLine='Import Completed at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 #Auth Variables #$username = "AzureReaderSVC@mfcgd.com" #$securePwd = Get-Content E:\Scripts\Encrypted.txt | ConvertTo-SecureString -Key (1..16) #$AZCred = New-Object System.Management.Automation.PSCredential($username, $securePwd) #if($AZcred -eq $null) #{ $userID = "$([Environment]::UserName)@mfcgd.com" $AZcred = Get-Credential -message "Enter Account Azure admin account (usernameadm@mfcgd.com)" $userID ## To connect to Azure subscription #} Clear-AzContext -Force #Update-AzConfig -DefaultSubscriptionForLogin 391e38fd-83c2-4f86-9b88-9513ed4f1add #Connect-AzAccount -Credential $AZCred -Subscription 391e38fd-83c2-4f86-9b88-9513ed4f1add #Login-AzAccount -Credential $AZCred -TenantId $TenantId -ErrorAction Stop | Out-Null Connect-AzAccount -Subscription 391e38fd-83c2-4f86-9b88-9513ed4f1add Login-AzAccount -TenantId $TenantId -ErrorAction Stop | Out-Null Connect-AzureAD -Credential $AZCred | Out-Null #$token = Get-AzAccessToken #Connect-AzureAD -AadAccessToken $token -AccountId $AppId -TenantId $TenantId $OutLine='Login completed at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 # log into Azure RM #Login-AzureRmAccount #Variables you may need to change $ADServer = "AZNE2GDCP01.MFCGD.COM" $SubDescFile = $PSScriptRoot + "\" + "SubscriptionDescription.csv" $SubDesc = Import-Csv -Path $SubDescFile -Delimiter ',' $OutLine='Create DropZone completed at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 #$DestinationDirectory = "DropZone:" $DestinationDirectory = $PSScriptRoot + "\" + $CurrentDateAndTime $outfile1 = $DestinationDirectory+'\Azure_Priv_Subscription_Review.csv' #LAN ID,Subscription,Subscription Description,Role $outfile2 = $DestinationDirectory+'\AzureACLGroups_group_subscription.csv' #Group Name,Group Description,Subscription,Subscription Description,Role $outfile3 = $DestinationDirectory+'\AzureACLGroups_members.csv' #GroupName,UserID,Full Name $outfile4 = $DestinationDirectory+'\PAIRLOG.txt' $outfile5 = $DestinationDirectory+'\AzureNestedACLGroups.csv' $outfile6 = $DestinationDirectory+'\Azure_Priv_Subscription_EXCEPTIONS.csv' $outfile7 = $DestinationDirectory+'\Azure_Priv_Resources.csv' $outfile8 = $DestinationDirectory+'\AzureACLGroups_group_Reources.csv' $outfile9 = $DestinationDirectory+'\Azure_Priv_Resources_EXCEPTIONS.csv' $outfile11 = $DestinationDirectory+'\Azure_ManagementGroup_Review.csv' $OutLine='Variables Created at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 # create the temporary directory Write-Host "Temporary Directory: $DestinationDirectory" New-Item $DestinationDirectory -type directory -Force | Out-Null if(-not (Test-Path $DestinationDirectory)) { Write-Host "Error creating temporary directory; exiting" -ForegroundColor Red Exit } Write-Host "First Output File: $outfile1" Write-Host "Second Output File: $outfile2" Write-Host "Third Output File: $outfile3" Write-Host "Fourth Output File: $outfile4" Write-Host "Fifth Output File: $outfile5" Write-Host "Sixth Output File: $outfile6" Write-Host "Seventh Output File: $outfile7" #write file headers GroupName,UserID,Full Name $Outline = 'created: '+$CurrentDateAndTime+[char]13+[char]10+'created by: '+$AZCred.UserName echo 'ObjectType,DisplayName,LAN ID,Subscription,Subscription Description,Role' > $outfile1 echo 'ObjectType,Group Name,Group Description,Subscription,Subscription Description,Role' > $outfile2 echo 'GroupName,GroupDescription,OdataType,Full Name,UserID' > $outfile3 echo $outline >$outfile4 echo 'GroupName,GroupDescription,OdataType,NestedGroup,UserID' > $outfile5 echo 'ObjectType,DisplayName,LAN ID,Subscription,Subscription Description,Role' > $outfile6 echo 'ObjectType,DisplayName,LAN ID,Subscription,Subscription Description,Scope,Role' > $outfile7 echo 'ObjectType,Group Name,Group Description,Subscription,Subscription Description,Scope,Role' > $outfile8 echo 'ObjectType,DisplayName,LAN ID,Subscription,Subscription Description,Scope,Role' > $outfile9 #echo 'ObjectType,DisplayName,LAN ID,ManagementGroup,MG ID,Role' > $outfile11 $OutLine='Headers completed at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 # get a list of all Azure RM subscriptions $subscriptionsList = Get-AzSubscription $OutLine='Found/Processing '+$subscriptionsList.Count+' Subscriptions' echo $Outline >> $outfile4 $AllGroups=@() $GroupObjectIDs=@() $OutLine='Get Subscriptions completed at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 foreach ($sub in $subscriptionsList) { $OutLine='Processing '+$sub.State+' Subscription '+$sub.Name+' '+$sub.Id+' started at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile4 echo $Outline >> $outfile10 Write-Host "Subscription name is: " $sub.Name -ForegroundColor Cyan Write-Host "Subscription ID is: " $sub.Id -ForegroundColor Cyan Set-AzContext -SubscriptionObject $sub #Select-AzSubscription -Subscription $sub.Name | Out-Null $SubscriptionDescription='TBD' if ($sub.Name -in $SubDesc.Subscription){ $SubscriptionDescription=$SubDesc.Where({$_.Subscription -eq $sub.Name}).Description Write-Host "Subscription description is: " $SubscriptionDescription -ForegroundColor Cyan } Else { $SubscriptionDescription='TBD' } if ($sub.State -eq 'Disabled') { $Outline = 'Subscription: '+$sub.Name+' is Disabled' echo $Outline >> $outfile4 } # get all role assignments $OutLine='Start Get-AzRoleAssignment at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 $SubRoles=Get-AzRoleAssignment -IncludeClassicAdministrators if ($SubRoles -eq $null) { $outline = 'Subscription: '+$sub.Name+' contains no roles' echo $outline >> $outfile4 } $OutLine='Processing ResourceGroups at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 foreach ($role in $SubRoles.where{$_.Scope -like '*resourcegroups*'}) { #echo 'OdataType,DisplayName,LAN ID,Subscription,Subscription Description,ResourceGroup,Resource,Role' > $outfile7 if ($role.ObjectType -eq 'Group') { #group $outline='' $allGroups += $role.DisplayName $GroupObjectIDs += $role.ObjectID $GroupObj = Get-AzADGroup -DisplayName $role.DisplayName # -Properties Name, Description $GroupObj.Description = $GroupObj.Description+"," $outline = $role.ObjectType+','+$GroupObj.DisplayName.Replace(",","")+','+$GroupObj.Description.Replace(",",";").Replace([char]10,"|").Replace([char]13,"|")+','+$sub.Name+','+$sub.ID+','+$role.Scope+','+$role.RoleDefinitionName echo $outline >> $outfile8 } elseif ($role.ObjectType -eq 'User') { #Service Principal, User, other $outline='' if ($role.SignInName -eq $null) { $outline = '!'+$role.ObjectType+','+$role.DisplayName.Replace(",","")+','+$role.SignInName.Replace(",","")+','+$sub.Name+','+$sub.ID.Replace([char]10,"|").Replace([char]13,"|")+','+$role.Scope+','+$role.RoleDefinitionName $erroroutline = 'User Missing SignInName: '+$outline echo $erroroutline >> $Outfile4 $erroroutline = '*' echo $outline >> $outfile7 } Else { #$checkname = $role.SignInName $outline='' if ($role.SignInName -ilike '*onmicrosoft.com') { $outline = $role.ObjectType+','+$role.DisplayName+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.Scope+','+$role.RoleDefinitionName echo $outline >> $outfile9 } else { $outline = $role.ObjectType+','+$role.DisplayName.ToLower().Replace("@mfcgd.com","")+','+$role.SignInName.ToLower().Replace("@mfcgd.com","")+','+$sub.Name+','+$sub.ID+','+$role.Scope+','+$role.RoleDefinitionName echo $outline >> $outfile7 } } } elseif ($role.ObjectType -eq 'ServicePrincipal') { $outline = $role.ObjectType+','+$role.DisplayName+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.Scope+','+$role.RoleDefinitionName echo $outline >> $outfile9 } elseif ($role.ObjectType -eq 'Unknown'){ $outline = $role.ObjectType+','+$role.ObjectId+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.Scope+','+$role.RoleDefinitionName $erroroutline = 'Deleted Account: '+'['+$role.ObjectId+'] within '+$sub.Name+','+$role.Scope+','+$role.RoleDefinitionName echo $erroroutline >> $Outfile4 $erroroutline = '*' echo $outline >> $outfile9 } else { $outline = $role.ObjectType+','+$role.ObjectId+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.Scope+','+$role.RoleDefinitionName #$erroroutline = 'Deleted Account: '+'['+$role.ObjectId+'] within '+$sub.Name+','+$role.Scope+','+$role.RoleDefinitionName #echo $erroroutline >> $Outfile4 $erroroutline = '*' echo $outline >> $outfile9 } } $OutLine='Processing NON-ResourceGroups at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 foreach ($role in $SubRoles.where{$_.Scope -notlike '*resourcegroups*'}) { if ($role.ObjectType -eq 'Group') { #group $outline='' $allGroups += $role.DisplayName $GroupObjectIDs += $role.ObjectID $GroupObj = Get-AzADGroup -DisplayName $role.DisplayName if ($GroupObj.count -eq 1) { $GroupObj.Description = $GroupObj.Description +','} $outline = $role.ObjectType+','+$GroupObj.DisplayName.Replace(",","")+','+$GroupObj.Description.Replace(",","").Replace([char]10,"|").Replace([char]13,"|")+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName echo $outline >> $outfile2 } elseif ($role.ObjectType -eq 'User') { #Service Principal, User, other $outline='' if ($role.SignInName -eq $null) { $outline = '!'+$role.ObjectType+','+$role.DisplayName.Replace(",","")+','+$role.SignInName.Replace(",","").Replace([char]10,"|").Replace([char]13,"|")+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName $erroroutline = 'User Missing SignInName: '+$outline echo $erroroutline >> $Outfile4 $erroroutline = '*' echo $outline >> $outfile1 } Else { #$checkname = $role.SignInName $outline='' if ($role.SignInName -ilike '*onmicrosoft.com') { $outline = $role.ObjectType+','+$role.DisplayName+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName echo $outline >> $outfile6 } else { $outline = $role.ObjectType+','+$role.DisplayName.ToLower().Replace("@mfcgd.com","")+','+$role.SignInName.ToLower().Replace("@mfcgd.com","")+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName echo $outline >> $outfile1 } } } elseif ($role.ObjectType -eq 'ServicePrincipal') { $outline = $role.ObjectType+','+$role.DisplayName+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName echo $outline >> $outfile6 } elseif ($role.ObjectType -eq 'Unknown') { $outline = $role.ObjectType+','+$role.ObjectId+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName $erroroutline = 'Deleted Account: '+'['+$role.ObjectId+'] within '+$sub.Name+','+$role.RoleDefinitionName echo $erroroutline >> $Outfile4 $erroroutline = '*' echo $outline >> $outfile6 } else { $outline = $role.ObjectType+','+$role.ObjectId+','+$role.SignInName+','+$sub.Name+','+$sub.ID+','+$role.RoleDefinitionName #$erroroutline = 'Deleted Account: '+'['+$role.ObjectId+'] within '+$sub.Name+','+$role.RoleDefinitionName #echo $erroroutline >> $Outfile4 #$erroroutline = '*' echo $outline >> $outfile6 } } Write-Host 'Completed subscription' } #user > $Member.OdataType.Length -eq 21 #group > $Member.OdataType.Length -eq 22 #device > $Member.OdataType.Length -eq 23 #ServicePrincipal > $Member.OdataType.Length -eq 33 write-host 'Processing Groups' $OutLine='Processing Groups at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 $UniqueGroup = $AllGroups | Sort-Object | Get-Unique $UniqueObjects = $GroupObjectIDs | Sort-Object | Get-Unique #search for nested ACLs Write-Host 'first pass:searching for nested ACLs' foreach ($GrpObject in $UniqueGroup) { #write-host $GrpObject $GroupObj = Get-AzADGroup -DisplayName $GrpObject if ($GroupObj.Count -gt 1) { Write-Host $GroupObj.Displayname 'contains more than one object' $Outline = $GroupObj.Displayname +'contains more than one object...Using ObjectId '+ $GroupObj.id[0] + (Get-Date).ToString('yyyyMMddhhmmss') echo $outline >> $outfile4 echo $outline >> $outfile10 $Members = Get-azADGroupMember -GroupObjectId $GroupObj.Id[0] } else { $Members = Get-azADGroupMember -GroupObjectId $GroupObj.Id } foreach ($Member in $Members) { if ($Member.OdataType.Length -eq 22) { #echo $Member.name $AllGroups += $Member.DisplayName } } } $UniqueGroup = $AllGroups | Sort-Object | Get-Unique $OutLine='Processing Nested Groups at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 Write-Host 'second pass:searching for nested ACLs' foreach ($GrpObject in $UniqueGroup) { #write-host $GrpObject $GroupObj = Get-AzADGroup -DisplayName $GrpObject if ($GroupObj.Count -gt 1) { Write-Host $GroupObj.Displayname 'contains more than one object' $Outline = $GroupObj.Displayname +'contains more than one object...Using ObjectId '+ $GroupObj.id[0] + (Get-Date).ToString('yyyyMMddhhmmss') echo $outline >> $outfile4 echo $outline >> $outfile10 $Members = Get-azADGroupMember -GroupObjectId $GroupObj.Id[0] } else { $Members = Get-azADGroupMember -GroupObjectId $GroupObj.Id } foreach ($Member in $Members) { if ($Member.OdataType.Length -eq 22) { #echo $Member.name $AllGroups += $Member.DisplayName } } } $UniqueGroup = $AllGroups | Sort-Object | Get-Unique $OutLine='Processing Group memberships at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 #---- write-host 'Pulling group memberships' foreach ($GrpObject in $UniqueGroup) { #write-host $GrpObject #$GroupObj = Get-AzADGroup -server "MFCGD.COM" -Identity $GrpObject -Properties Name, Description $GroupObj = get-azadgroup -DisplayName $GrpObject if ($GroupObj.Count -gt 1) { Write-Host $GroupObj.Displayname 'contains more than one object' $Outline = $GroupObj.Displayname +'contains more than one object...Using ObjectId '+ $GroupObj.id[0] + (Get-Date).ToString('yyyyMMddhhmmss') echo $outline >> $outfile4 echo $outline >> $outfile10 $GroupObj = get-azadgroup -ObjectId $GroupObj.id[0] } $GroupObj.Description = $GroupObj.Description + ',' $Members = @() $Members = Get-AzADGroupMember -GroupObjectId $GroupObj.Id write-host $GrpObject $Members.count -ForegroundColor White foreach ($Member in $Members) { #GroupName,DisplayName,UserPrincipalName if ($Member.UserPrincipalName -eq $null) { $Outline=$GrpObject+','+$GroupObj.Description.Replace(",","").Replace([char]10,"|").Replace([char]13,"|")+','+$Member.OdataType+','+$Member.DisplayName.Replace(",","")+',' $erroroutline = 'Group Line Inconsistancy: '+$outline echo $erroroutline >> $Outfile4 $erroroutline = '*' } Else { $Outline=$GrpObject+','+$GroupObj.Description.Replace(",","").Replace([char]10,"|").Replace([char]13,"|")+','+$Member.OdataType+','+$Member.DisplayName.Replace(",","")+','+$Member.UserPrincipalName.ToLower().Replace("@mfcgd.com","") } if ($Member.OdataType.Length -eq 21) { echo $outline >> $outfile3 } Else { echo $outline >> $outfile5 } } } $Outline = 'Processing Completed: '+(Get-Date).ToString('yyyyMMddhhmmss') echo $outline >> $outfile4 echo $outline >> $outfile10 $OutLine='Copying to dev Dropzone at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 # set output file and check for writability $DEVPath = "\\azwapnpairdev01\PairinputFiles\AzureDownload" #"\\AZUWVGISPAIRP01\PairInputFiles\AzureDownload" #$UATPath ="\\AZUWVGISPAIRP02\PairInputFiles\AzureDownload" $UATPath ="\\AZWAPPPAIRINV01.mfcgd.com\PairInputFiles\AzureDownload" $ProdPath ="\\azuwvgispairp03\PairInputFiles\AzureDownload" New-PSDrive -Name "DEVDropZone" -PSProvider FileSystem -Root $DEVPath -Credential $AZCred New-PSDrive -Name "UATDropZone" -PSProvider FileSystem -Root $UATPath -Credential $AZCred New-PSDrive -Name "PRODDropZone" -PSProvider FileSystem -Root $ProdPath -Credential $AZCred #<## DO NOT COPY/MOVE - DEV TESTING #Copying to UAT Drop Zone $Outline = 'Copying to '+$UATPath+': ' +(Get-Date).ToString('yyyyMMddhhmmss') echo $outline >> $outfile4 $UATDROP="UATDropZone:\"+ $CurrentDateAndTime Copy-Item -Destination "UATDropZone:\" -Force -Path $DestinationDirectory Copy-Item -Destination $UATDROP -Force -Path $outfile1 Copy-Item -Destination $UATDROP -Force -Path $outfile2 Copy-Item -Destination $UATDROP -Force -Path $outfile3 Copy-Item -Destination $UATDROP -Force -Path $outfile4 Copy-Item -Destination $UATDROP -Force -Path $outfile5 Copy-Item -Destination $UATDROP -Force -Path $outfile6 Copy-Item -Destination $UATDROP -Force -Path $outfile7 Copy-Item -Destination $UATDROP -Force -Path $outfile8 Copy-Item -Destination $UATDROP -Force -Path $outfile9 $OutLine='Moving to prod Dropzone at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10 #Moving to PROD Drop Zone $Outline = 'Moving to '+$ProdPath+': ' +(Get-Date).ToString('yyyyMMddhhmmss') echo $outline >> $outfile4 Move-Item -Destination "ProdDropZone:\" -Force -Path $DestinationDirectory ##> Remove-PSDrive -Name "DEVDropZone" Remove-PSDrive -Name "UATDropZone" Remove-PSDrive -Name "ProdDropZone" #$Outline = 'Move completed: '+(Get-Date).ToString('yyyyMMddhhmmss') #echo $outline >> $outfile4 # clear all variables #Remove-Variable * -ErrorAction SilentlyContinue $OutLine='Completed at '+(Get-Date).ToString('yyyyMMddhhmmss') echo $Outline >> $outfile10