Category Archives: Security

Automated Stale Device Cleanup in Azure Active Directory using a runbook

As with cleaning up inactive guest users, inactive devices also pose several issues for organizations.

Microsoft recommends cleaning up stale devices after 90 days, but does not provide a service option or automation to do so.

Therefore, another runbook you may run to just report on your inactive devices, or to automatically (and optionally periodically) clean up inactive devices in your environment when the removeInactiveDevices switch is supplied.

Managed identity

When run locally, interactive sign in is required. When running as a runbook in Azure automation, the Managed Identity of the automation account is leveraged. This requires you to set Device.ReadWrite.All or Device.Read.All permissions depending on if you want to script to do the cleanup as well.

Autopilot / on premises devices

Note that the script will log an error (and not attempt to delete the device) when a device is an autopilot record (not a real device) or when the device is synced from an on-premises active directory.

Download

Download get-AzureADInactiveDevices.ps1 from Gitlab

Disclaimer

As always, the script is provided as-is and should be reviewed and then used at your own risk.

Guest User Last Sign-in date time in Azure Active Directory and automatic cleanup

Azure AD’s sign in logs also only go back 30 days, which makes it highly recommended to stream Azure AD’s sign in logs to a Log Analytics workspace (Azure Monitor). You just need one single P1 license in your tenant to be able to enable this.

However, even if you don’t stream your sign in logs, Microsoft does keep track of when an account last signed in.

My script gets the last sign in data of all guest accounts in your tenant, without any dependencies other than the Az PS module.

If a guest user has never signed in, the creationDate is used to determine inactivity. Otherwise either the last interactive or last non interactive sign in is used (whichever is most recent).

Additionally, the script can also be configured to automatically clean up any guest accounts that have been inactive for a given number of days by using the -removeInactiveGuests switch.

Even in large environments, processing only takes a few minutes at most.

Download

Download the script from my Gitlab here:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/get-AzureAdInactiveGuestUsers.ps1

Limitations

Microsoft started using these properties in april 2020, so accounts active before that will seem like they have never been active.

Scheduling

This script supports running non-interactive as a runbook in Azure Automation if you supply the -nonInteractive switch. Before this will work, you’ll have to enable Managed Identity on your automation account and run a small script to assign graph permissions to the Managed Identity: AuditLog.Read.All and Organization.Read.All

Disclaimer

As always this script is provided as-is and should be reviewed and then used at your own risk.

Sharepoint permission auditing

When auditing a Sharepoint environment, an important component is permissions;

  • invited users
  • sharing links
  • inherited permissions
  • unique permissions
  • broken inheritance
  • sites, webs
  • lists, libraries

I’ve heavily modified Salaudeen┬áRajack’s work to share a more fully featured and faster PowerShell auditing script that will dump all unique permissions (up to item level, recursively) for all sharepoint sites (including O365 group sites). For files, folders, sites, libraries, etc etc.

It retrieves membership of groups so the resulting CSV file contains all permissions, with exception of the “Everyone” group, which is listed as a group instead.

You can find the script here: https://gitlab.com/Lieben/assortedFunctions/-/blob/master/get-SPOPermissions.ps1

Usage

  • the script uses device based logon, just follow the prompts.
  • don’t forget to first set permissions on all sites for your admin account, see script header for an example
  • requires the PnP module
  • you can exclude specific sites or users from the report if needed, configure siteIgnoreList or principalIgnoreList for that
  • Runtime on an environment with over 1000 sites and millions of objects was about 6 hours. If you environment is too large, contact me and I can perhaps introduce e.g. multi-threading.

Lightweight LAPS solution for INtune (MEM)

Managing local admin accounts using Intune has a lot of quirks, my tele-colleague Rudy Ooms has already written extensively about this. He also wrote a PowerShell solution to rotate a specific local admin’s password and had the genius idea of using Proactive Remediations (a MEM feature) to display passwords to admins, integrated / free in the Intune Console.

However, I felt I needed a more lightweight solution that;

  • does not require/modify registry keys
  • does not store the password locally
  • can encrypt the password if desired
  • does not need separate detection and remediation scripts
  • automatically provisions a local admin account
  • can remove any other local admin accounts if desired
  • can whitelist approved admin (groups)
  • is language/locale-agnostic (e.g. ‘Administrators’ vs ‘Administradores’….)

Thus LeanLAPS was born!

To install/use:

1. head into the Proactive Remediations section of MDE and click Create script package:

2. Fill out some details:

3. Download and doublecheck the config of LeanLAPS.ps1 (e.g. configure if other local admins should be removed, what the local admin name should be and the password length). Make sure to use NotePad++ / that the file stays UTF-8 Encoded without a BOM.

4. Set both the detection and remediation script to LeanLAPS.ps1 and run it in 64 bit:

5. Assign to a group and deploy. By default it will run every day, but you can also let it run more or less frequently, which determines how often the password is reset (hourly in below example):

6. Deploy, and then click on the script package:

7. Go to Device status and add both output columns:

Congratulations, you can now see the current local admin passwords for all managed Windows 10 devices!

Note: if you wish to trigger a quick remediation, delete the correct keys under Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Execution and Reports in the client’s registry, then restart the IntuneManagementExtension service and the remediation will re-run within 5 minutes.

8. If you want to display an encrypted password in Intune, generate a public and private key and configure the resulting values in gui.ps1 and leanLAPS.ps1

RBAC

If you provide e.g. your helpdesk with the correct Intune roles, they will be able to see local admin passwords as reported by above solution:

GUI

The community, in the form of Colton Lacy, also added an optional GUI frontend for LeanLAPS which you could use for e.g. helpdesk staff:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/leanLAPS/gui.ps1

Self scheduling cleanup job for MEM kiosk machines

Normally I’d recommend using the Unified Write Filter in Windows 10 to keep Kiosk machines in a semi-decent state.

For a customer that did not have this luxury, I wrote a tiny self-scheduling PowerShell script that will run as SYSTEM and clean up any of the specified folders in any of the user profiles on the machine.

This example can be used for many purposes to drop a script and maintain a scheduled task. Redeploying it will overwrite the dropped script and scheduled task as per the new config.

Git link or direct code here:

#Module name:      Invoke-wipeSpecifiedProfileFolders
#Author:           Jos Lieben
#Author Blog:      https://www.lieben.nu
#Date:             18-12-2020
#License:          Free to use and modify non-commercially, leave headers intact. For commercial use, contact me
#Purpose:          Delete all files in the specified folder names in all user profiles on the machine, self-installs as a scheduled task
#Setup:            Deploy to machines, in system context
#Requirements:     Windows 10 build 1803 or higher

$folderWipeList = "Downloads,Network Shortcuts,Temp,Documents" #comma seperated list of folders to wipe

$desiredScriptFolder = Join-Path $env:ProgramData -ChildPath "Lieben.nu"
$desiredScriptPath = Join-Path $desiredScriptFolder -ChildPath "Invoke-wipeSpecifiedProfileFolders.ps1"
if(![System.IO.Directory]::($desiredScriptFolder)){
    New-Item -Path $desiredScriptFolder -Type Directory -Force
}
Start-Transcript -Path (Join-Path $desiredScriptFolder -ChildPath "\folderWiperInstaller.log")

Write-Output "Configuring scheduled task..."

$taskname = "Invoke-wipeSpecifiedProfileFolders"
$taskdescription = "Delete all files in the specified folder names in all user profiles on the machine"
$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "-NoProfile -WindowStyle Hidden -NonInteractive -ExecutionPolicy ByPass -File `"$desiredScriptPath`""
$triggers =  @()
$triggers += (New-ScheduledTaskTrigger -AtStartup)
$triggers += (New-ScheduledTaskTrigger -Daily -At 23:00)
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
$task = Register-ScheduledTask -Action $action -Trigger $triggers -TaskName $taskname -Description $taskdescription -Settings $settings -User "System" -Force -RunLevel Highest

Write-Output "task info: "
Write-Output $task

Write-Output "Writing script file to local disk..."

$scriptContent = "
Start-Transcript -Path (Join-Path $desiredScriptFolder -ChildPath `"\folderWiper.log`")
`$folderWipeList = `"$folderwipeList`"
`$folderWipeList = `$folderwipeList.Split(`",`")
Get-ChildItem 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList' | ForEach-Object {
    `$rootPath =  `$_.GetValue('ProfileImagePath') 
    Write-Output `"Parsing folders in `$rootPath`"
    `$childItems = `$Null
    `$childItems = Get-ChildItem -Path `$rootPath -Directory -ErrorAction SilentlyContinue -Recurse -Force | where{`$folderWipeList -contains `$_.BaseName}
    if(`$childItems){
    	foreach(`$folder in `$childItems){
    		Write-Output `"Wiping matched folder: `$(`$folder.FullName)`"
    		Get-ChildItem -Path `$folder.FullName -Force -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue -Recurse -Confirm:`$False
    	}
    }
}

Stop-Transcript"

Set-Content -Value $scriptContent -Path $desiredScriptPath -Force -Confirm:$False

Write-Output "Starting script as task for the first time..."

Start-ScheduledTask -InputObject $task

Write-Output "Install script has finished running"

Stop-Transcript