diff --git a/BitLocker - Save Keys to IT Glue export.cpt b/BitLocker - Save Keys to IT Glue export.cpt new file mode 100644 index 0000000..232f69f Binary files /dev/null and b/BitLocker - Save Keys to IT Glue export.cpt differ diff --git a/Bitlocker - Save Keys to ITGlue.ps1 b/Bitlocker - Save Keys to ITGlue.ps1 new file mode 100644 index 0000000..f22a2c8 --- /dev/null +++ b/Bitlocker - Save Keys to ITGlue.ps1 @@ -0,0 +1,280 @@ +# This script reads the BitLocker information for every drive on the system and will record the key for any drive that has one (will overwrite an old key with the current one). +# It requires PowerShell 5.0 or newer. The ITGlueAPI only requires PowerShell 3.0, but since +# many of the commands in the script are using PowerShell 5.0 functions for ease of use, 5.0 is required. + + +# DattoRMM device GUID +$varDattoRMMDeviceID = (Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\CentraStage).DeviceID + + +function Get-CmdletExist { + param ( + [parameter(Mandatory=$true)] + [string] + $cmdname + ) + if (Get-Command $cmdname -errorAction SilentlyContinue) { + return $true + } + else{ + return $false + } +} + + +function Update-RequiredModules { + # Updates the various modules and package providers needed to successfully install the ITGlueAPI module. + Install-PackageProvider NuGet -Force + Install-Module -Name PowerShellGet -Force -AllowClobber + Update-Module -Name PowerShellGet +} + +function Install-ITGlueAPI { + # Installs the latest ITGlueAPI to the computer. + Install-Module -Name ITGlueAPI -Force -AllowClobber + Update-Module -Name ITGlueAPI +} + +function Get-ModuleVersion { + # Returns the version of the installed module provided. + param ( + [Parameter(Mandatory=$true)] + [string] + $Module + ) + $tmp = (get-module -ListAvailable -name $Module | select-object -Property Version | sort-object | Select-Object -Last 1).version + return $tmp +} + +function Get-PSGalleryModuleVersion { + # Returns the latest version of the module provided as available on PSGallery. + param ( + [Parameter(Mandatory=$true)] + [string] + $Module + ) + $tmp = (Find-Module -name $Module | select-object -Property Version | sort-object | Select-Object -Last 1).version + return $tmp + +} + +function Get-ITGlueDevice { + param ( + [Parameter(Mandatory=$true)] + [string] + $DattoRMMDeviceID + ) + + #Parse through a configuration list of configs with the local computer name to find the configuration with the matching GUID from Datto RMM + Foreach ($Comp in (Get-ITGlueConfigurations -filter_name $env:COMPUTERNAME).data) { + IF ($Comp.attributes.'asset-tag' -match $DattoRMMDeviceID) { + $Config = $Comp + } + Else { + $Config = $null + } + } + + return $config +} + +# Check for module logging +function Test-RegistryEntry { + param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + $Path, + [parameter(mandatory=$true)] + [ValidateNotNullorEmpty()] + $key, + [parameter(mandatory=$true)] + [string] + $value + ) + try { + # The first line causes the try section to exit out if the key doesn't exist. The out-null prevents errors from showing up in the stderr. + # The second line will only run if the first line ran successfully without errors. + $vartmp = (Get-ItemProperty -Path $path | select-object -ExpandProperty $key -ErrorAction Stop | out-null) + $vartmp = (Get-ItemProperty -Path $path | select-object -ExpandProperty $key) + if (($vartmp) -eq "1"){ + return $true + } + else { + return $false + } + } + catch { + return $false + } + +} + +write-host "Running environment checks: `n" + +# Checks to make sure the required variables have been configured in the Datto RMM component. + +if ($null -eq $env:ITGlueAPIKey){ + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= ITGlueAPIKey missing in component variables. =" + write-output "= =" + write-output "================================================================" + exit 1 +} + +# Checking that PowerShell is at least version 5.0 +if ($PSVersionTable.psversion.Major -lt 5) { + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= PowerShell 5.0 or later is required. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +else { + write-output "PowerShell version $($psversiontable.psversion.major).$($psversiontable.psversion.minor) installed." +} + + + +# Tests to see if Powershell module logging is enabled. This will allow the local account passwords +# and API keys to be discovereable in the Powershell event logs. Unless your environment has no +# untrusted administrative accounts, this is an EXTREME security risk. + +if (((Test-registryentry -path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging' -key 'EnableModuleLogging' -value '1') -eq $true) -or ((Test-registryentry -path 'HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ModuleLogging' -key 'EnableModuleLogging' -value '1') -eq $true)) { + write-output "================================================================" + write-output "= =" + write-output "= WARNING: =" + write-output "= PowerShell Module Logging is enabled. This will allow =" + write-output "= highly sensitive information to be discovered in the logs. =" + write-output "= This script will now exit. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +else { + write-output "Powershell module logging is not enabled." +} + +# Makes sure that the execution policy is properly configured. The script itself is ran with the -bypass command, +# but the imported modules will not work without using RemoteSigned. + +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -force | Out-Null + +# Checks to make sure the install-module command is available. All other functions require this environmental condition. +# This section updates the powershell environment if necessary. +if (Get-CmdletExist -cmdname "Install-Module"){ + write-host "`nChecking required module versions..." + # PowerShell 5.1 ships with an old version of PowerShellGet. If this has not been updated on the system, install the new version. + # Nuget is required for this script to work. Install nuget if it is missing. + + # Since NuGet is required for the PSGalleryModuleVersion check to work, install it if it is missing. + if(Get-PackageProvider | where-object {"NuGet" -contains $_.name}){ + write-host "Nuget package provider already installed." + } + else{ + write-host "Installing NuGet package provider." + Install-PackageProvider NuGet -Force + } + $varCurrentPSGet = Get-PSGalleryModuleVersion -Module "PowerShellGet" + if ([version](get-moduleversion -Module "PowerShellGet") -lt [version]$varCurrentPSGet){ + write-host "The installed version of PowerShellGet is older than the current release of $varCurrentPSGet." + write-host "Updating NuGet and PowerShellGet to latest versions.`n" + Update-RequiredModules + } + else { + write-host "The installed version of PowerShellGet is greater than or equal to the current release of $varCurrentPSGet." + } + + # Checks the currently installed version of ITGlueAPI. If the version is out of date, update to the latest version. + $varCurrentITGlueAPI = Get-PSGalleryModuleVersion -Module "ITGlueAPI" + if ([version](get-moduleversion -Module "ITGlueAPI") -lt [version]$varCurrentITGlueAPI){ + write-host "The installed version of ITGlueAPI is older than the current release of $varCurrentITGlueAPI." + write-host "Updating ITGlueAPI to latest versions.`n" + Install-ITGlueAPI + } + else { + write-host "The installed version of ITGlueAPI is greater than or equal to the current release of $varCurrentITGlueAPI." + } +} +else { + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= Install-Module cmdlet does not exist. =" + write-output "= ITGlue integration requies the Install-Module functionality. =" + write-output "= Please update the PowerShell environment on this system. =" + write-output "= =" + write-output "================================================================" + exit 1 +} + + + +# Activate the ITGlueAPI module +write-host "`nActivating ITGlueAPI..." +Import-Module ITGlueAPI +Add-ITGlueAPIKey -api_key $env:ITGlueAPIKey + +# Find the computer configuration in ITGlue using the new Get-ITGlueDevice function +write-host "Locating $env:COMPUTERNAME in ITGlue." +$varITGlueDevice=(Get-ITGlueDevice -DattoRMMDeviceID $varDattoRMMDeviceID) + +if ($null -eq $varITGlueDevice){ + Write-Output "`nUnable to locate device $env:COMPUTERNAME in ITGlue. Exiting script." + exit 1 +} + +# Identify all the Bitlocker volumes. +$BitlockerVolumes = Get-BitLockerVolume + +# Get the Recovery Key for each volume and store it in IT Glue +$BitlockerVolumes | + ForEach-Object { + $MountPoint = $_.MountPoint + $RecoveryKey = [string]($_.KeyProtector).RecoveryPassword + #This section builds the password details with the following: + # type - must be set to passwords + # organization-id - single tick quote because of the hyphen, this is required for locating the configuration + # name - the name of the password in quotes as text + # username - the username in quotes as text + # password - using the randomly generated password above + # resource_id - required for locating the resource + # resource_type = 'Configuration' + $data = @{ + type = "passwords" + attributes = @{ + 'organization-id' = $varITGlueDevice.attributes.'organization-id' + name = "Bitlocker Key for $MountPoint" + password = $RecoveryKey + resource_id = $varITGlueDevice.'id' + resource_type = 'Configuration' + } + } + if ($RecoveryKey.Length -gt 5) { + #This line retrieves the old password using the organization, configuration name, and password name (will not error if no password is found) + $OldBLPass = Get-ITGluePasswords -filter_organization_id $varITGlueDevice.attributes.'organization-id' -filter_cached_resource_name $varITGlueDevice.attributes.name -filter_name "Bitlocker Key for $MountPoint" + #Checks if the url for the config matches with the parent url of the embedded password - if so, the password is updated, if not then the password is created + If ($OldBLPass.data.attributes.'parent-url' -eq $varITGlueDevice.attributes.'resource-url') { + write-host "Existing Bitlocker recovery key found in ITGlue - confirming if changed..." + If ((Get-ITGluePasswords -id ($OldBLPass.data.id) -show_password 1).data.attributes.password -eq $RecoveryKey) { + write-host "The recorded BitLocker recovery key for $MountPoint is current." + } + Else { + write-host "The BitLocker key for $MountPoint has changed - updating record in IT Glue." + Set-ITGluePasswords -id $OldBLpass.data.id -data $data + } + } + Else { + write-host "The Bitlocker key for $MountPoint has not been recorded. Saving record to ITGlue." + New-ITGluePasswords -data $data + } + } + else { + write-host "Volume $mountpoint is not encrypted." + } + } + + diff --git a/LAPS for All Export.cpt b/LAPS for All Export.cpt new file mode 100644 index 0000000..80dd75d Binary files /dev/null and b/LAPS for All Export.cpt differ diff --git a/LAPSforALL.ps1 b/LAPSforALL.ps1 new file mode 100644 index 0000000..390dda2 --- /dev/null +++ b/LAPSforALL.ps1 @@ -0,0 +1,523 @@ +# This script will set a local account to a random password and update this password in ITGlue. +# It requires PowerShell 5.0 or newer. The ITGlueAPI only requires PowerShell 3.0, but since +# many of the commands in the script are using PowerShell 5.0 functions for ease of use, 5.0 is required. +# +# Sets various environmental variables needed for the script +# + +##################################################################################################################### +# # +# NOTE: Variables that use the format of $env: are pushed as part of the environment from the # +# DattoRMM platform. These will be outlined here with the relevant description. For ease of testing, the script # +# was made with hard-coded variables which were later changed to be set by the DattoRMM component. Removing the # +# DattoRMM variables and replacing them with the respective value will allow the script to run as a stand-alone # +# process. # +# # +# $env:LocalAdminAccount - This is the account that will be made local admin on the device. If it doesn't exist, # +# it will be created. # +# # +# $env:AccountDescription - This is the description for the local admin account to be shown in computer management. # +# # +# $env:MaxPasswordAge - This is the maximum password age in days. If the current password is older than X days, # +# it will be updated. # +# # +# $env:ITGlueAPIKey - This is the ITGlue API key to use. # +# # +# $env:DisableOtherLocalAccounts - True/False. Sets weather to disable other local accounts. Domain only. # +# # +##################################################################################################################### + + +# This script will set a local account to a random password and update this password in ITGlue. +# It requires PowerShell 5.0 or newer. The ITGlueAPI only requires PowerShell 3.0, but since +# many of the commands in the script are using PowerShell 5.0 functions for ease of use, 5.0 is required. +# +# Sets various environmental variables needed for the script +# + + +# The local account on the computer that will be checked. If the account does not exist, it will be created. +# It will also be added to the local administrators group if it isn't already. +$varLocalAccount = $env:LocalAdminAccount +$varLocalAccountDescription = $env:AccountDescription + +# Set the maximum password age +$maxPWAge = $env:MaxPasswordAge + +# DattoRMM device GUID +# This is used to confirm that the correct device in ITGlue is being updated. This is the unique device ID used in DattoRMM. +$varDattoRMMDeviceID = (Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\CentraStage).DeviceID + +# If set to true, will disable any other local accounts if the computer is part of a domain. +$varDisableOtherLocalAccounts = $env:DisableOtherLocalAccounts + + +function Get-CmdletExist { + # Part of the update process requires certain powershell cmdlets to be installed on the device. + # This function checks for the cmdlet's existance. + param ( + [parameter(Mandatory=$true)] + [string] + $cmdname + ) + if (Get-Command $cmdname -errorAction SilentlyContinue) { + return $true + } + else{ + return $false + } +} + +function Get-LocalPasswordAge { + # Returns the account's password age in days for the specified account. + param ( + [parameter(Mandatory=$true)] + [string] + $localaccount + ) + $now = Get-Date + $account = get-localuser $localaccount + $pwLastSet = $account.PasswordLastSet + $PWAge = ($now - $pwLastSet).days + return $PWAge +} + +function Update-RequiredModules { + # Updates the various modules and package providers needed to successfully install the ITGlueAPI module. + # NuGet must be updated to the latest version. If this is the first time the script has been ran, it is likely outdated. + # PowerShellGet must be installed and the latest version. The commands force install the module, and updates the module to latest version if it is already installed. + # The latest versions of NuGet and PowerShellGet are required in order to check the version number of modules from the PowerShell Gallery repository. + Install-PackageProvider NuGet -Force + Install-Module -Name PowerShellGet -Force -AllowClobber + Update-Module -Name PowerShellGet +} + +function Install-ITGlueAPI { + # Installs the latest ITGlueAPI to the computer. + # This performs a forced install of the module if it is missing, and updates it to the latest version if it is already installed. + Install-Module -Name ITGlueAPI -Force -AllowClobber + Update-Module -Name ITGlueAPI +} + +function Get-ModuleVersion { + # Returns the version of the installed module provided. + param ( + [Parameter(Mandatory=$true)] + [string] + $Module + ) + $tmp = (get-module -ListAvailable -name $Module | select-object -Property Version | sort-object | Select-Object -Last 1).version + return $tmp +} + +function Get-PSGalleryModuleVersion { + # Returns the latest version of the module provided as available on PSGallery. + param ( + [Parameter(Mandatory=$true)] + [string] + $Module + ) + $tmp = (Find-Module -name $Module | select-object -Property Version | sort-object | Select-Object -Last 1).version + return $tmp + +} + +function Get-ITGlueDevice { + # This function tries to find the matching device in ITGlue for the current machine. + param ( + [Parameter(Mandatory=$true)] + [string] + $DattoRMMDeviceID + ) + + # Parse through a configuration list of configs with the local computer name to find the configuration with the matching GUID from Datto RMM. + # In case there are multiple matching computer names, this will find the specific ITGlue object which contains the corresponding device ID from DattoRMM. + Foreach ($Comp in (Get-ITGlueConfigurations -filter_name $env:COMPUTERNAME).data) { + IF ($Comp.attributes.'asset-tag' -match $DattoRMMDeviceID) { + $Config = $Comp + } + Else { + $Config = $null + } + } + + return $config +} + +# Check for module logging +function Test-RegistryEntry { + # Check for registry key and object. + param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + $Path, + [parameter(mandatory=$true)] + [ValidateNotNullorEmpty()] + $key, + [parameter(mandatory=$true)] + [string] + $value + ) + try { + # The first line causes the try section to exit out if the key doesn't exist. The out-null prevents errors from showing up in the stderr. + # The second line and after will only run if the first line ran successfully without errors. + $vartmp = (Get-ItemProperty -Path $path | select-object -ExpandProperty $key -ErrorAction Stop | out-null) + $vartmp = (Get-ItemProperty -Path $path | select-object -ExpandProperty $key) + if (($vartmp) -eq "1"){ + return $true + } + else { + return $false + } + } + catch { + return $false + } + +} + +write-host "Running environment checks: `n" + +# Checks to make sure the script is only being ran on workstations. If it is ran on +# a server, the script will simply exit with a success. + # $osInfor.ProductType returns back the type of system the script is running on (1, 2 or 3). + # 1 - Workstation + # 2 - Domain Controller + # 3 - Server +$osInfo = Get-WmiObject -Class Win32_OperatingSystem + +if ($osInfo.ProductType -gt 1) { + write-output "================================================================" + write-output "= =" + write-output "= INFO: =" + write-output "= This script will only run on workstations. =" + write-output "= =" + write-output "================================================================" + exit 0 +} + +# Checks to make sure the required variables have been configured in the Datto RMM component. +if ($null -eq $varLocalAccount){ + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= LocalAdminAccount missing in component variables. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +if ($null -eq $varLocalAccountDescription){ + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= AccountDescription missing in component variables. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +if ($null -eq $env:ITGlueAPIKey){ + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= ITGlueAPIKey missing in component variables. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +if ($null -eq $maxPWAge){ + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= MaxPasswordAge missing in component variables. =" + write-output "= =" + write-output "================================================================" + exit 1 +} + +# Checking that PowerShell is at least version 5.0 +if ($PSVersionTable.psversion.Major -lt 5) { + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= PowerShell 5.0 or later is required. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +else { + write-output "PowerShell version $($psversiontable.psversion.major).$($psversiontable.psversion.minor) installed." +} + + + +# Tests to see if Powershell module logging is enabled. This will allow the local account passwords +# and API keys to be discovereable in the Powershell event logs. Unless your environment has no +# untrusted administrative accounts, this is an EXTREME security risk. + +if (((Test-registryentry -path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging' -key 'EnableModuleLogging' -value '1') -eq $true) -or ((Test-registryentry -path 'HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ModuleLogging' -key 'EnableModuleLogging' -value '1') -eq $true)) { + write-output "================================================================" + write-output "= =" + write-output "= WARNING: =" + write-output "= PowerShell Module Logging is enabled. This will allow =" + write-output "= highly sensitive information to be discovered in the logs. =" + write-output "= This script will now exit. =" + write-output "= =" + write-output "================================================================" + exit 1 +} +else { + write-output "Powershell module logging is not enabled." +} + +# Makes sure that the execution policy is properly configured. The script itself is ran with the -bypass command, +# but the imported modules will not work without using RemoteSigned. + +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -force | Out-Null + +# Checks to make sure the install-module command is available. All other functions require this environmental condition. +# This section updates the powershell environment if necessary. +if (Get-CmdletExist -cmdname "Install-Module"){ + write-host "`nChecking required module versions..." + # PowerShell 5.1 ships with an old version of PowerShellGet. If this has not been updated on the system, install the new version. + # Nuget is required for this script to work. Install nuget if it is missing. + + # Since NuGet is required for the PSGalleryModuleVersion check to work, install it if it is missing. + if(Get-PackageProvider | where-object {"NuGet" -contains $_.name}){ + write-host "Nuget package provider already installed." + } + else{ + write-host "Installing NuGet package provider." + Install-PackageProvider NuGet -Force + } + $varCurrentPSGet = Get-PSGalleryModuleVersion -Module "PowerShellGet" + if ([version](get-moduleversion -Module "PowerShellGet") -lt [version]$varCurrentPSGet){ + write-host "The installed version of PowerShellGet is older than the current release of $varCurrentPSGet." + write-host "Updating NuGet and PowerShellGet to latest versions.`n" + Update-RequiredModules + } + else { + write-host "The installed version of PowerShellGet is greater than or equal to the current release of $varCurrentPSGet." + } + + # Checks the currently installed version of ITGlueAPI. If the version is out of date, update to the latest version. + $varCurrentITGlueAPI = Get-PSGalleryModuleVersion -Module "ITGlueAPI" + if ([version](get-moduleversion -Module "ITGlueAPI") -lt [version]$varCurrentITGlueAPI){ + write-host "The installed version of ITGlueAPI is older than the current release of $varCurrentITGlueAPI." + write-host "Updating ITGlueAPI to latest versions.`n" + Install-ITGlueAPI + } + else { + write-host "The installed version of ITGlueAPI is greater than or equal to the current release of $varCurrentITGlueAPI." + } +} +else { + # There are a handful of machines which have broken PowerShell environments. + # If the earlier test for install-module failed, throw an error so the system can be fixed. + write-output "================================================================" + write-output "= =" + write-output "= ERROR: =" + write-output "= Install-Module cmdlet does not exist. =" + write-output "= ITGlue integration requies the Install-Module functionality. =" + write-output "= Please update the PowerShell environment on this system. =" + write-output "= =" + write-output "================================================================" + exit 1 +} + + + + +# Activate the ITGlueAPI module +write-host "`nActivating ITGlueAPI..." +Import-Module ITGlueAPI +Add-ITGlueAPIKey -api_key $env:ITGlueAPIKey + +# Find the computer configuration in ITGlue. +# If the DattoRMM device ID cannot be found in ITGlue, there is likely a sync issue of some sort. +# The script throws an error here so the issue can be investigated and corrected. +write-host "Locating $env:COMPUTERNAME in ITGlue." +$varITGlueDevice=(Get-ITGlueDevice -DattoRMMDeviceID $varDattoRMMDeviceID) + +if ($null -eq $varITGlueDevice){ + Write-Output "`nUnable to locate device $env:COMPUTERNAME in ITGlue. Exiting script." + exit 1 +} + +# This section generates a random password (length, #of symbols) and convert it to a secure string for Windows. +# ITGlue is not able to parse a secure string, so an unencrypted string must be used. +write-host "`nGenerating secure password..." +Add-Type -AssemblyName System.Web +$PW = [System.Web.Security.Membership]::GeneratePassword(16,4) +$SecurePW = ($PW | ConvertTo-SecureString -AsPlainText -Force) + +#This section builds the password details with the following: +# type - must be set to passwords +# organization-id - single tick quote because of the hyphen, this is required for locating the configuration +# name - the name of the password in quotes as text +# username - the username in quotes as text +# password - using the randomly generated password above +# resource_id - required for locating the resource +# resource_type = 'Configuration' +$data = @{ + type = "passwords" + attributes = @{ + 'organization-id' = $varITGlueDevice.attributes.'organization-id' + name = "Local $varLocalAccount Admin" + username = "$varLocalAccount" + password = $PW + resource_id = $varITGlueDevice.'id' + resource_type = 'Configuration' + } +} + + +#This line retrieves the old password in ITGlue using the organization, configuration name, and password name (will not error if no password is found) +$OldPass = Get-ITGluePasswords -filter_organization_id $varITGlueDevice.attributes.'organization-id' -filter_cached_resource_name $varITGlueDevice.attributes.name -filter_name "Local $varLocalAccount Admin" + + +# Checks if the url for the config matches with the parent url of the embedded password - if so, the password is updated, if not then the password is created +# If the password is not already in ITGlue, force a password change and save to ITGlue regardless of date of last password change. +write-host "`nChecking to see if password exists in ITGlue." +If ($OldPass.data.attributes.'parent-url' -eq $varITGlueDevice.attributes.'resource-url') { + write-host "`nPassword data exists in ITGlue." + # Check the specified local account and see if it exists. If not, create it. + # Set the password to the new secure password and enabled the account. (Just in case it was disabled.) + Write-Host "`nChecking local account $varLocalAccount..." + If ((Get-LocalUser $varLocalAccount).name -eq $varLocalAccount) { + write-host "Local account $varLocalAccount already exists." + write-host "`nChecking password age..." + $currentPWAge = Get-LocalPasswordAge -localaccount $varlocalaccount + if ($maxPWAge -le $currentPWage) { + write-host "Password is older than $maxPWAge day(s). Updating password." + Set-LocalUser -Name $varLocalAccount -Password $SecurePW + Enable-LocalUser -Name $varLocalAccount + write-host "Updating existing password in ITGlue." + Set-ITGluePasswords -id $Oldpass.data.id -data $data + } + else { + write-host "Password has been changed in the last $maxPWAge day(s). No update necessary." + Enable-LocalUser -Name $varLocalAccount + } + } + else { + write-host "Local account $varLocalAccount does not exist. Creating account. Setting password." + New-LocalUser -Name $varLocalAccount -Password $SecurePW -Description $varLocalAccountDescription + write-host "Updating existing password in ITGlue." + Set-ITGluePasswords -id $Oldpass.data.id -data $data + } +} +else { + write-host "`nNo password entry found in ITGlue. Forcing password change." + If ((Get-LocalUser $varLocalAccount).name -eq $varLocalAccount) { + write-host "Local account $varLocalAccount already exists." + Set-LocalUser -Name $varLocalAccount -Password $SecurePW + Enable-LocalUser -Name $varLocalAccount + write-host "Creating new embedded password in ITGlue." + New-ITGluePasswords -data $data + } + else { + write-host "Local account $varLocalAccount does not exist. Creating account. Setting password." + New-LocalUser -Name $varLocalAccount -Password $SecurePW -Description $varLocalAccountDescription + write-host "Creating new embedded password in ITGlue." + New-ITGluePasswords -data $data + } +} + + + + +# Checks to make sure the account is part of the local administrators group and adds it if not. +if (get-localgroupmember -group "Administrators" -member $varLocalAccount -ErrorAction SilentlyContinue) { + Write-Host "`nLocal account $varLocalAccount is already part of the local Administrators group." +} +else { + Add-LocalGroupMember -Group Administrators -Member $varLocalAccount + Write-Host "`nLocal account $varLocalAccount has been added to the local Administrators group." +} + +if ($env:DisableAdminVariations -eq $true){ + # Disables all the various iterations and misspellings of our Admin accounts. For example, if you use "MSPLocalAdmin" for your admin account, + # and someone created an account called "MSP Local Admin" instead, you would add this incorrect variation to this section. When the script is ran, it would create + # the correct local admin account name and then verify all the local accounts on the machine, and if it finds a match to a "wrong" version of your specific + # admin account, that account would then be disabled. These specific accounts will be disabled regardless of disabling other local accounts on the machine. + # This account variations list to disable is generated outside the scope of the script. + write-output "`nDisabling various Local Admin Account iterations..." + foreach ($user in get-localuser) { + switch ($user.name) { + $varLocalAccount {write-host "No action taken for $($user.name)."; break} +# This section below should be uncommented and cloned for each and every variation you want to disable. + +# "" { +# if ($user.enabled -eq $true){ +# write-host "Disabling local user $($user.name)." +# disable-localuser $user.name +# } +# else { +# write-host "User $($user.name) is already disabled." +# } +# } + + Default {} + } + } +} + + +# If set to do so, disable other local accounts on the system. +# If the script does not disable any local accounts, it performs an audit of the existing accounts +# and their enabled/disabled status for reference. +if ($varDisableOtherLocalAccounts -eq $true){ + # If the computer is part of a domain, disable the other local accounts. + # This is a sanity check so this won't actually do anything if the computer is not on a domain. + if ((Get-WmiObject -class win32_ComputerSystem).PartOfDomain) { + write-output "`nDisabling other local accounts..." + foreach ($user in get-localuser) { + switch ($user.name) { + # This leaves the account that was updated alone along with other local accounts which should be left as-is. + # If other local accounts need to be left alone, they can be added to this switch statement. + $varLocalAccount {write-host "No action taken for $($user.name)."} + "WDAGUtilityAccount" {write-host "No action taken for $($user.name)."} + + Default { + if ($user.enabled -eq $true){ + write-host "Disabling local user $($user.name)." + disable-localuser $user.name + } + else { + write-host "User $($user.name) is already disabled." + } + + } + } + } + } + else { + write-output "`nListing status of local accounts..." + foreach ($user in get-localuser) { + if ($user.enabled -eq $true){ + write-host "User $($user.name) is currently enabled." + } + else { + write-host "User $($user.name) is already disabled." + } + + } + } +} +else { + write-output "`nListing status of local accounts..." + foreach ($user in get-localuser) { + if ($user.enabled -eq $true){ + write-host "User $($user.name) is currently enabled." + } + else { + write-host "User $($user.name) is already disabled." + } + + } +} + +# Everything is done. Log an 'all finished' line and exit. + + +write-host "`nLocal admin account policy update has completed." \ No newline at end of file diff --git a/README-bitlocker.md b/README-bitlocker.md new file mode 100644 index 0000000..2040bba --- /dev/null +++ b/README-bitlocker.md @@ -0,0 +1,14 @@ +BitLocker - Save to IT Glue + + + +Function: +This script will locate all saved BitLocker recovery keys on a system and save each as an embedded password for that system's configuration in IT Glue. Keys are created per volume, so running this script on a system with multiple encrypted volumes will result in multiple embedded passwords, each named for the volume (BitLocker key for C:, BitLocker key for J:, etc.). To be clear, this type of BitLocker recovery key is the one that is saved to the system for automatic decryption of encrypted drives. This will not save the keys for any drives that need a key manually entered for decryption (typically encrypted portable drives) because that key is not saved to the system. + + + +Specific Use: +This Powershell script is intended to run as a component in Datto RMM with the IT Glue API Key embedded in the component as an environment variable to prevent exposure. Datto RMM saves a GUID for the system in the registry, which the script uses to match to the correct configuration in IT Glue. This is probably similar for other RMM tools, but these sections may need to be rewritten to obtain and compare a different RMM GUID. + +Purpose: +Microsoft has made BitLocker freely available for Windows 10 Pro. For security reasons, all devices which leave the premise or are publicly accessible, or may contain sensitive information (HIPAA, PCI-compliance, safe-harbor), should be encrypted with BitLocker. Even though we instruct our clients to save their BitLocker recovery key in a safe place, many users and clients misplace or lose their recovery key. As such, we felt it was necessary to capture and record the BitLocker recovery key for each encrypted volume and manage that data centrally in ITGlue. With this new ability to automatically save and record the BitLocker recovery keys centrally in ITGlue for all managed devices, we now have a way to recover the data in the case of motherboard replacement, drive issues, or for data recovery. With DattoRMM, saving this data to a user defined field (which is typically what would be done), would only store the current key. If a failing drive were replaced and a new drive installed and encrypted, the saved key would be replaced with the new one and no way to access the data from the old drive. ITGlue's password versioning provides a fail-safe for recovering data from a drive as previous entries are maintained. With the ease of recovering BitLocker encrypted data, we can expand our client's deployment of BitLocker to all applicable devices without needing to worry about them losing or misplacing their BitLocker recovery key. \ No newline at end of file diff --git a/README.md b/README.md index 830936c..72746f4 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ # api-contest +# LAPS for All +# +# Short description: +# LAPS for All creates a unique password for the local admin account on a Windows workstation and saves the password to ITGlue. +# +# Long Description: +# LAPS for All integrates DattoRMM endpoints with their corresponding ITGlue configurations and sets a unique local admin password for each machine. The password for each system +# is saved within the ITGlue platform as an embedded password attached to the configuration. This script is designed specifically for the functionaility available inside the DattoRMM and +# ITGlue platforms and may not be able to be ported to other RMM platforms. +# +# +# LAPSforALL.ps1 - The main powershell script +# LAPS for ALL Export.cpt - Component for DattoRMM. This component is normally what would be used. This component would imported into the DattoRMM platform, and then ran from DattoRMM. +# +# +# Items of note: +# 1) This will work for all endpoints, including those not on domains. +# 2) Passwords are unique for each device and stored inside of the device's ITGlue configuration. +# 3) All required prequisites are automatically installed as needed. (Installation of .Net Framework and PowerShell 5.0 is handled outside the scope of the script.) +# 4) Required components are automatically updated to the latest available version. +# 5) Passwords are dynamically created utilizing the web security functions from .Net framework. +# 7) Local admin account name can be customized. (This is used for both the local account on the machine and the name of the embedded password entry in ITGlue.) +# 8) Customizable max age for local admin password. (Only passwords older than the max age will be reset/updated.) +# 9) Option to disable other local accounts on the endpoint. (This will only function on domain joined PCs.) +# 10) Safety checks in place to make sure that this script will not execute on servers. +# 11) If the ITGlue configuration cannot be found, no changes password or account changes will be made. +# 12) If PowerShell module logging is enabled, the script will automatically exit to prevent sensitive information from being recorded in the Windows Event logs. +# 13) The script logs changes and status to std-out. +# +# +# +# +# +# *There is no number 6 - ref: https://tvtropes.org/pmwiki/pmwiki.php/Main/ThereIsNoRuleSix \ No newline at end of file