diff --git a/PowerShell/JumpCloud Commands Gallery/Mac Commands/Application Installs/Mac - Install Chrome DMG.md b/PowerShell/JumpCloud Commands Gallery/Mac Commands/Application Installs/Mac - Install Chrome DMG.md index 79e760629..5c02ad246 100644 --- a/PowerShell/JumpCloud Commands Gallery/Mac Commands/Application Installs/Mac - Install Chrome DMG.md +++ b/PowerShell/JumpCloud Commands Gallery/Mac Commands/Application Installs/Mac - Install Chrome DMG.md @@ -10,7 +10,7 @@ mac ``` # DMG Download URL -DownloadUrl="https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg" +DownloadUrl="https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg" ### Modify below this line at your own risk! @@ -173,7 +173,7 @@ echo "Deleted /tmp/$TempFolder" #### Description -Installs Google Chrome from the DMG file available for download from the link: `https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg`. +Installs Google Chrome from the DMG file available for download from the link: `https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg`. This command creates a temporary folder in the /tmp directory and to downloads the DMG file to this folder. diff --git a/PowerShell/JumpCloud Module/Docs/Remove-JCUser.md b/PowerShell/JumpCloud Module/Docs/Remove-JCUser.md index 2e52b217b..baed772e5 100644 --- a/PowerShell/JumpCloud Module/Docs/Remove-JCUser.md +++ b/PowerShell/JumpCloud Module/Docs/Remove-JCUser.md @@ -14,12 +14,14 @@ Removes a JumpCloud User ### Username (Default) ``` -Remove-JCUser [-Username] [-force] [] +Remove-JCUser [-Username] [-force] [-CascadeManager ] + [] ``` ### UserID ``` -Remove-JCUser -UserID [-ByID] [-force] [] +Remove-JCUser -UserID [-ByID] [-force] [-CascadeManager ] + [] ``` ## DESCRIPTION @@ -40,6 +42,28 @@ PS C:\> Remove-JCUser cclemons -Force ``` Removes the JumpCloud User with Username 'cclemons' using the -Force Parameter. A warning message will not be presented to confirm this operation. +If the cclemons is a manager of other users, the `Force` parameter will clear cclemons' subordinates `manager` field. In other words if a user is managed by cclemons, removing cclemons will also remove that user's manager field in JumpCloud. + +### Example 3 +```powershell +PS C:\> Remove-JCUser cclemons -CascadeManager null +``` + +Removes the Jumpcloud user with Username 'cclemons'. If `cclemons` manages other JumpCloud users, those user's will have their manager field set to null. Note. This command as the same effect as running `Remove-JCUser cclemons -Force` + +### Example 4 +```powershell +PS C:\> Remove-JCUser cclemons -CascadeManager automatic +``` + +Removes the JumpCloud user with the username 'cclemons' and automatically update's their subordinates manager field to `cclemons` manager. Ex. If `cclemons` is a manager and is also managed by another user with username: `some.manager`, the users managed by `cclemons` will be reassigned to `some.manager` upon `cclemons` removal. If `cclemons` is not managed by anyone, the manager field for the `cclemons` subordinates will be set to null. + +### Example 5 +```powershell +PS C:\> Remove-JCUser cclemons -CascadeManager User -CascadeManagerUser some.manager +``` + +Removes the JumpCloud user with the username `cclemons`. If `cclemons` is a manager, their subordinates will be reassigned to the manager specified by the provided username/id with CascadeManagerUser parameter. In this case, `cclemons` subordinates will be managed by the user with username: `some.manager` after `cclemons` is removed. ## PARAMETERS @@ -59,6 +83,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -CascadeManager +A SwitchParameter for Cascading the manager of the user to the users managed by the user. NULL, AUTOMATIC (bubble up), ID (prompt for manager ID) + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: +Accepted values: NULL, Automatic, User + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -force A SwitchParameter which suppresses the warning message when removing a JumpCloud User. diff --git a/PowerShell/JumpCloud Module/JumpCloud.psd1 b/PowerShell/JumpCloud Module/JumpCloud.psd1 index 53a2d199b..f8769ac2a 100644 --- a/PowerShell/JumpCloud Module/JumpCloud.psd1 +++ b/PowerShell/JumpCloud Module/JumpCloud.psd1 @@ -38,37 +38,38 @@ PowerShellVersion = '4.0' # Name of the PowerShell host required by this module # PowerShellHostName = '' -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @('JumpCloud.SDK.DirectoryInsights', 'JumpCloud.SDK.V1', 'JumpCloud.SDK.V2') -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = 'Add-JCAssociation', 'Add-JCCommandTarget', 'Add-JCGsuiteMember', @@ -108,62 +109,62 @@ FunctionsToExport = 'Add-JCAssociation', 'Add-JCCommandTarget', 'Add-JCGsuiteMem 'Set-JCUserGroupLDAP', 'Update-JCDeviceFromCSV', 'Update-JCModule', 'Update-JCMSPFromCSV', 'Update-JCUsersFromCSV' -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() -# Variables to export from this module -VariablesToExport = '*' + # Variables to export from this module + VariablesToExport = '*' -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = 'New-JCAssociation' + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = 'New-JCAssociation' -# DSC resources to export from this module -# DscResourcesToExport = @() + # DSC resources to export from this module + # DscResourcesToExport = @() -# List of all modules packaged with this module -# ModuleList = @() + # List of all modules packaged with this module + # ModuleList = @() -# List of all files packaged with this module -# FileList = @() + # List of all files packaged with this module + # FileList = @() -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ - PSData = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = 'JumpCloud', 'DaaS', 'Jump', 'Cloud', 'Directory' + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'JumpCloud', 'DaaS', 'Jump', 'Cloud', 'Directory' - # A URL to the license for this module. - LicenseUri = 'https://github.com/TheJumpCloud/support/blob/master/PowerShell/LICENSE' + # A URL to the license for this module. + LicenseUri = 'https://github.com/TheJumpCloud/support/blob/master/PowerShell/LICENSE' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/TheJumpCloud/support/wiki' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/TheJumpCloud/support/wiki' - # A URL to an icon representing this module. - IconUri = 'https://avatars1.githubusercontent.com/u/4927461?s=200&v=4' + # A URL to an icon representing this module. + IconUri = 'https://avatars1.githubusercontent.com/u/4927461?s=200&v=4' - # ReleaseNotes of this module - ReleaseNotes = 'https://git.io/jc-pwsh-releasenotes' + # ReleaseNotes of this module + ReleaseNotes = 'https://git.io/jc-pwsh-releasenotes' - # Prerelease string of this module - # Prerelease = '' + # Prerelease string of this module + # Prerelease = '' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - # ExternalModuleDependencies = @() + # External dependent modules of this module + # ExternalModuleDependencies = @() - } # End of PSData hashtable + } # End of PSData hashtable -} # End of PrivateData hashtable + } # End of PrivateData hashtable -# HelpInfo URI of this module -HelpInfoURI = 'https://github.com/TheJumpCloud/support/wiki' + # HelpInfo URI of this module + HelpInfoURI = 'https://github.com/TheJumpCloud/support/wiki' -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' } diff --git a/PowerShell/JumpCloud Module/Public/Users/Remove-JCUser.ps1 b/PowerShell/JumpCloud Module/Public/Users/Remove-JCUser.ps1 index a522e1a9b..4e414266d 100644 --- a/PowerShell/JumpCloud Module/Public/Users/Remove-JCUser.ps1 +++ b/PowerShell/JumpCloud Module/Public/Users/Remove-JCUser.ps1 @@ -1,6 +1,5 @@ function Remove-JCUser () { [CmdletBinding(DefaultParameterSetName = 'Username')] - param ( [Parameter(Mandatory, @@ -26,25 +25,39 @@ UserID has an Alias of _id. This means you can leverage the PowerShell pipeline HelpMessage = 'Use the -ByID parameter when the UserID is passed over the pipeline to the Remove-JCUser function. The -ByID SwitchParameter will set the ParameterSet to ''ByID'' which will increase the function speed and performance.')] [Switch] $ByID, - + # Do not use $CascadeManager if $force is used [Parameter(HelpMessage = 'A SwitchParameter which suppresses the warning message when removing a JumpCloud User.')] [Switch] - $force + $force, + [Parameter(HelpMessage = 'A SwitchParameter for Cascading the manager of the user to the users managed by the user. NULL, AUTOMATIC (bubble up), ID (prompt for manager ID)')] + [ValidateSet('NULL', 'Automatic', 'User')] + [string]$CascadeManager ) + DynamicParam { + # Create a dynamic parameter to get the -CascadeManagerId + if ($PSBoundParameters['CascadeManager'] -eq 'User') { + $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary + $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] + + $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute + $paramAttributes.Mandatory = $true + $paramAttributesCollect.Add($paramAttributes) + $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("CascadeManagerUser", [string], $paramAttributesCollect) + $paramDictionary.Add('CascadeManagerUser', $dynParam1) + return $paramDictionary + } + } begin { Write-Debug 'Verifying JCAPI Key' if ([System.String]::IsNullOrEmpty($JCAPIKEY)) { Connect-JConline } - Write-Debug 'Populating API headers' $hdrs = @{ - 'Content-Type' = 'application/json' 'Accept' = 'application/json' 'X-API-KEY' = $JCAPIKEY - } if ($JCOrgID) { @@ -52,13 +65,43 @@ UserID has an Alias of _id. This means you can leverage the PowerShell pipeline } $deletedArray = @() - - if ($PSCmdlet.ParameterSetName -eq 'Username' ) { - $UserHash = Get-DynamicHash -Object User -returnProperties username - $UserCount = ($UserHash).Count - Write-Debug "Populated UserHash with $UserCount users" + # If $cascadeManager and $force are used, throw an error + if ($CascadeManager -and $force) { + Throw "Cannot use -CascadeManager and -Force together. Please use one or the other." } + $UserHash = Get-DynamicHash -Object User -returnProperties 'username', 'manager' + # Validate dynamic parameter + if ($PSBoundParameters['CascadeManager'] -eq 'User') { + $CascadeManagerValue = $PSBoundParameters['CascadeManagerUser'] + + # Validate if ID or Username is passed + $regexPattern = [Regex]'^[a-z0-9]{24}$' + if ($CascadeManagerValue -match $regexPattern) { + # Validate if the Id is a JC User from the $UserHash + if ($UserHash.ContainsKey($CascadeManagerValue)) { + $CascadeManagerId = $CascadeManagerValue + Write-Debug "$CascadeManagerId is a valid JumpCloud User" + } else { + Write-Error "UserId $($CascadeManagerValue) does not exist. Please enter a valid UserID." + # Throw the script + Throw + } + } else { + # Validate if the Username is a JC User from the $UserHash + if ($UserHash.Values.username -contains ($CascadeManagerValue)) { + Write-Debug "$CascadeManagerValue is a valid JumpCloud User usr" + # Get the UserID from the $UserHash + $CascadeManagerId = $UserHash.GetEnumerator().Where({ $_.Value.username -contains ($CascadeManagerValue) }).Name + Write-Debug "CascadeManagerId is a valid JumpCloud User $CascadeManagerId" + + } else { + Write-Error "Username $($CascadeManagerValue) does not exist. Please enter a valid Username." + # Throw the script + Throw + } + } + } } process { if ($PSCmdlet.ParameterSetName -eq 'Username' ) { @@ -67,56 +110,226 @@ UserID has an Alias of _id. This means you can leverage the PowerShell pipeline } else { Throw "Username does not exist. Run 'Get-JCUser | Select-Object username' to see a list of all your JumpCloud users." } + } elseif ($PSCmdlet.ParameterSetName -eq 'UserID') { + # Validate if the Id is a JC User from the $UserHash + if ($UserHash.ContainsKey($UserID)) { + $Username = $UserHash.GetEnumerator().Where({ $_.Name -contains ($UserID) }) | Select-Object -ExpandProperty Value | Select-Object -ExpandProperty username + Write-Host "UserID $($UserId) is a valid JumpCloud User: $($Username)" + } else { + Write-Error "UserId $($UserID) does not exist. Please enter a valid UserID." + # Throw the script + Throw + } } - - if ($PSCmdlet.ParameterSetName -eq 'UserID' ) { - $Username = $UserID + # Check if the user is a manager + if ($UserHash.Values.manager -contains ($UserID)) { + $isManager = $true + # Count the number of users the manager is managing + # $managerCount = ($UserHash.Values.manager -eq $UserID).Count + # Save each user the manager is managing in a list + $managedUsers = $UserHash.GetEnumerator().Where({ $_.Value.manager -eq $UserID }).Name + Write-Debug "Manager $($Username) is managing $managedUsers users" + $hasManagerId = Get-JcSdkUser -Id $UserID | Select-Object -ExpandProperty manager + Write-Debug "Manager $($Username) is managed by $hasManagerId" + } else { + $isManager = $false + Write-Debug "User $($Username) is not a manager" } if (!$force) { - try { - $URI = "$JCUrlBasePath/api/systemusers/$UserID" - Write-Warning "Are you sure you wish to delete user: $Username ?" -WarningAction Inquire - $delete = Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) - $Status = 'Deleted' - } catch { - $Status = $_.ErrorDetails - } + if ($PSBoundParameters['CascadeManager'] -and $isManager) { + Write-Debug "Switching on $CascadeManager" + Switch ($CascadeManager) { - $FormattedResults = [PSCustomObject]@{ - 'User' = $Username - 'Results' = $Status - } + 'NULL' { + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=null" + Write-Host "Deleting user: $Username" -ForegroundColor Yellow + $Status = 'Deleted' + } + 'Automatic' { + if ($hasManagerId) { + Write-Host "Deleting user: $Username and cascading its managed users manager to $($hasManagerId)" -ForegroundColor Yellow + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=$($hasManagerId)" + $Status = "Deleted" - $deletedArray += $FormattedResults + } else { + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=null" + Write-Host "Deleting user: $Username" -ForegroundColor Yellow + $Status = "Deleted" + } + } + 'User' { + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=$($CascadeManagerId)" + Write-Host "Deleting user: $Username and cascading the managed users manager to the manager $($CascadeManagerId)" -ForegroundColor Yellow + $Status = "Deleted" + } + } + try { + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + } catch { + # Get the error details + $Status = $_.ErrorDetails + Write-Error $_.ErrorDetails + } + } elseif ($isManager -and !$PSBoundParameters['CascadeManager']) { + # Prompt for CascadeManager, user enters the ID of the new manager + $cascade_manager = Read-Host "User $($Username) is a manager and is managing $($managedUsers.Count) user(s). Do you want to reassign their managed users to another manager? (Y / N)" + if ($cascade_manager -eq 'Y') { + if ($hasManagerId) { + $managerUsername = $UserHash.GetEnumerator().Where({ $_.Name -contains ($hasManagerId) }) | Select-Object -ExpandProperty Value | Select-Object -ExpandProperty username + $cascade_manager = Read-Host "User $($Username) is managed by manager: $($managerUsername). Do you want to reassign the managed users to the manager: $($managerUsername)? (Y/N)" + if ($cascade_manager -eq 'Y') { + $newManagerId = $hasManagerId + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=$($newManagerId)" + Write-Host "Deleting user: $Username and cascading the managed users manager to the manager $($newManagerId)" -ForegroundColor Yellow + $prompt = Read-Host "Are you sure you wish to delete the user: $($Username)? (Y/N)" + if ($prompt -eq 'Y') { + try { + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + $Status = "Deleted" + } catch { + # Get the error details + $Status = $_.ErrorDetails + Write-Error $_.ErrorDetails + } - } + } elseif ($prompt -eq 'N') { + $Status = 'Not Deleted' + } else { + Write-Error "Please enter Y or N" + Throw + } + } elseif ($cascade_manager -eq 'N') { + $newManagerId = Read-Host "Enter the UserID of the new manager" + # Validate if the Id is a JC User + if ($UserHash.ContainsKey($newManagerId)) { + Write-Host "User $newManagerId is a valid JumpCloud User" + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=$($newManagerId)" + Write-Host "Deleting user: $Username and cascading the managed users manager to the manager $($newManagerId)" -ForegroundColor Yellow + $prompt = Read-Host "Are you sure you wish to delete the user: $($Username)? (Y/N)" + if ($prompt -eq 'Y') { + try { + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + $Status = "Deleted" + } catch { + # Get the error details + $Status = $_.ErrorDetails + Write-Error $_.ErrorDetails + } + } elseif ($prompt -eq 'N') { + $Status = 'Not Deleted' + } else { + Write-Error "Please enter Y or N" + Throw + } + } else { + Write-Error "User does not exist. Please enter a valid UserID." + # Throw the script + Throw + } + } + } else { + $newManagerId = Read-Host "Enter the UserID of the new manager" + # Validate if the Id is a JC User + if ($UserHash.ContainsKey($newManagerId)) { + Write-Host "User $newManagerId is a valid JumpCloud User" + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=$($newManagerId)" + Write-Host "Deleting user: $Username and cascading the managed users manager to the manager $($newManagerId)" -ForegroundColor Yellow + $prompt = Read-Host "Are you sure you wish to delete the user: $($Username)? (Y/N)" + if ($prompt -eq 'Y') { + try { + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + $Status = "Deleted" + } catch { + # Get the error details + $Status = $_.ErrorDetails + Write-Error $_.ErrorDetails + } + } elseif ($prompt -eq 'N') { + $Status = 'Not Deleted' + } else { + Write-Error "Please enter Y or N" + Throw + } + } else { + Write-Error "User does not exist. Please enter a valid UserID." + # Throw the script + Throw + } + } + } elseif ($cascade_manager -eq 'N') { + #$Status = Delete-JCUser -Id $UserID -managerId $null -Headers $hdrs -UserHash $UserHash + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=null" + Write-Host "Deleting user: $Username" -ForegroundColor Yellow + $prompt = Read-Host "Are you sure you wish to delete the user: $($Username)? (Y/N)" + if ($prompt -eq 'Y') { + try { + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + $Status = 'Deleted' + } catch { + $Status = $_.ErrorDetails + } + } elseif ($prompt -eq 'N') { + Write-Host "User not deleted" + $Status = 'Not Deleted' + } else { + Write-Error "Please enter Y or N" + Throw + } + } else { + Write-Error "Please enter Y or N" + Throw + } + } else { + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=null" + Write-Host "Deleting user: $Username" -ForegroundColor Yellow + $prompt = Read-Host "Are you sure you wish to delete the user: $($Username)? (Y/N)" + if ($prompt -eq 'Y') { + try { + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + $Status = "Deleted" + } catch { + Write-Error $_.ErrorDetails + } + + } elseif ($prompt -eq 'N') { + $Status = 'Not Deleted' + } else { + Write-Error "Please enter Y or N" + Throw + } + } + } if ($force) { try { - $URI = "$JCUrlBasePath/api/systemusers/$UserID" - $delete = Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) - $Status = 'Deleted' + $URI = "$JCUrlBasePath/api/systemusers/$($UserID)?cascade_manager=null" + Write-Host "Deleting user: $Username" -ForegroundColor Yellow + Invoke-RestMethod -Method Delete -Uri $URI -Headers $hdrs -UserAgent:(Get-JCUserAgent) | Out-Null + $Status = "Deleted" } catch { $Status = $_.ErrorDetails } + } + try { $FormattedResults = [PSCustomObject]@{ 'User' = $Username 'Results' = $Status } - - $deletedArray += $FormattedResults - + } catch { + $FormattedResults = [PSCustomObject]@{ + 'User' = $Username + 'Results' = $_.ErrorDetails + } + Write-Error $_.ErrorDetails } + $deletedArray += $FormattedResults } - end { - return $deletedArray - } } \ No newline at end of file diff --git a/PowerShell/JumpCloud Module/Tests/Public/Users/Remove-JCUser.Tests.ps1 b/PowerShell/JumpCloud Module/Tests/Public/Users/Remove-JCUser.Tests.ps1 index 2715b7d36..de7bc53b6 100755 --- a/PowerShell/JumpCloud Module/Tests/Public/Users/Remove-JCUser.Tests.ps1 +++ b/PowerShell/JumpCloud Module/Tests/Public/Users/Remove-JCUser.Tests.ps1 @@ -28,3 +28,109 @@ Describe -Tag:('JCUser') "Remove-JCUser 1.10" { } } +Describe -Tag:('JCUser') "Remove-JCUser 2.16.0" { + BeforeAll { + + } + It "Tests for CascadeManager param with Force" { + $ManagerUser = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + { Remove-JCUser -UserID $ManagerUser._id -CascadeManager NULL -force } | Should -Throw + # Clean up + Remove-JCUser -UserID $ManagerUser._id -force + } + + It "Removes JumpCloud Manager but also managed by a manager. Test CascadeManager param auto" { + $ManagerUser = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $ManagerUser2 = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $NewUser = New-RandomUser -Domain "delUser.$(New-RandomString -NumberOfChars 5)" | New-JCUser + + # Set the manager for user + Set-JCUser -UserID $ManagerUser._id -manager $ManagerUser2._id # ManagerUser2 is the manager of ManagerUser + Set-JCUser -UserID $NewUser._id -manager $ManagerUser._id + # Remove the manager and set the new manager + $RemoveUser = Remove-JCUser -UserID $ManagerUser._id -CascadeManager Automatic # Remove ManagerUser and should cascade to ManagerUser2 + + + # The manager should be removed and the new manager should be set + $RemoveUser.Results | Should -Be 'Deleted' + # The new manager should be set to ManagerUser2 + $manager = Get-JCUser -UserID $NewUser._id | Select-Object -ExpandProperty manager + $manager | Should -Be $ManagerUser2._id + # Clean up + Remove-JCUser -UserID $ManagerUser2._id -force + Remove-JCUser -UserID $NewUser._id -force + + } + + It "Removes JumpCloud user (manager) and set managed users manager to null. Test CascadeManager param null" { + $ManagerUser = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $NewUser = New-RandomUser -Domain "delUser.$(New-RandomString -NumberOfChars 5)" | New-JCUser + + # Set the manager for user + Set-JCUser -UserID $NewUser._id -manager $ManagerUser._id + # Remove the manager and set the new manager + + $RemoveUser = Remove-JCUser -UserID $ManagerUser._id -CascadeManager NULL + + # The manager should be removed and the new manager should be set + $RemoveUser.Results | Should -Be 'Deleted' + # The new manager should be set to ManagerUser2 + $manager = Get-JCUser -UserID $NewUser._id | Select-Object -ExpandProperty manager + $manager | Should -BeNullOrEmpty + # Clean up + Remove-JCUser -UserID $NewUser._id -force + } + + It "Removes JumpCloud user (manager) and set managed users manager to CascadeManagerUser. Test CascadeManager param User (Id)" { + $ManagerUser = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $ManagerUser2 = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $NewUser = New-RandomUser -Domain "delUser.$(New-RandomString -NumberOfChars 5)" | New-JCUser + + # Set the manager for user + Set-JCUser -UserID $NewUser._id -manager $ManagerUser._id + + # Remove the manager and set the new manager + $RemoveUser = Remove-JCUser -UserId $ManagerUser._id -CascadeManager User -CascadeManagerUser $ManagerUser2._id + # The manager should be removed and the new manager should be set + $RemoveUser.Results | Should -Be 'Deleted' + # The new manager should be set to ManagerUser2 + $manager = Get-JCUser -UserID $NewUser._id | Select-Object -ExpandProperty manager + $manager | Should -Be $ManagerUser2._id + # Clean up + Remove-JCUser -UserID $ManagerUser2._id -force + Remove-JCUser -UserID $NewUser._id -force + } + + It "Removes JumpCloud user (manager) and set managed users manager to CascadeManagerUser. Test CascadeManager param User (Username)" { + $ManagerUser = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $ManagerUser2 = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $NewUser = New-RandomUser -Domain "delUser.$(New-RandomString -NumberOfChars 5)" | New-JCUser + + # Set the manager for user + Set-JCUser -UserID $NewUser._id -manager $ManagerUser._id + + # Remove the manager and set the new manager + $RemoveUser = Remove-JCUser -UserId $ManagerUser._id -CascadeManager User -CascadeManagerUser $ManagerUser2.username + # The manager should be removed and the new manager should be set + $RemoveUser.Results | Should -Be 'Deleted' + # The new manager should be set to ManagerUser2 + $manager = Get-JCUser -UserID $NewUser._id | Select-Object -ExpandProperty manager + $manager | Should -Be $ManagerUser2._id + # Clean up + Remove-JCUser -UserID $ManagerUser2._id -force + Remove-JCUser -UserID $NewUser._id -force + } + + It "Removes JumpCloud user (manager) and set managed users manager to CascadeManagerUser. Test Invalid CascadeManagerUser param" { + $ManagerUser = New-RandomUser "PesterTest$(Get-Date -Format MM-dd-yyyy)" | New-JCUser + $NewUser = New-RandomUser -Domain "delUser.$(New-RandomString -NumberOfChars 5)" | New-JCUser + + # Set the manager for user + Set-JCUser -UserID $NewUser._id -manager $ManagerUser._id + + # Invalid CascadeManagerUser should throw an error + { Remove-JCUser -UserId $ManagerUser._id -CascadeManager User -CascadeManagerUser "InvalidJCUserxyz" } | Should -Throw + Remove-JCUser -UserID $ManagerUser._id -force + Remove-JCUser -UserID $NewUser._id -force + } +} \ No newline at end of file diff --git a/PowerShell/JumpCloud Module/Tests/SetupOrg.ps1 b/PowerShell/JumpCloud Module/Tests/SetupOrg.ps1 index 712c29844..63b65aff1 100644 --- a/PowerShell/JumpCloud Module/Tests/SetupOrg.ps1 +++ b/PowerShell/JumpCloud Module/Tests/SetupOrg.ps1 @@ -76,6 +76,7 @@ Try { If ($Commands) { #$null = Get-JCCommandResult | Remove-JCCommandResult -force $null = Get-JCCommand | Remove-JCCommand -force + #$null = Get-JCCommandResult | Remove-JCCommandResult -force Write-Host "[status] Removed commands: $($stopwatch.Elapsed)" } # Remove all RadiusServers from an org diff --git a/PowerShell/JumpCloud Module/en-Us/JumpCloud-help.xml b/PowerShell/JumpCloud Module/en-Us/JumpCloud-help.xml index d7e009760..a5b0b2cc7 100644 --- a/PowerShell/JumpCloud Module/en-Us/JumpCloud-help.xml +++ b/PowerShell/JumpCloud Module/en-Us/JumpCloud-help.xml @@ -17316,6 +17316,23 @@ PS C:\> New-JCPolicy -TemplateName darwin_Login_Window_Text -Values $policyV False + + CascadeManager + + A SwitchParameter for Cascading the manager of the user to the users managed by the user. NULL, AUTOMATIC (bubble up), ID (prompt for manager ID) + + + NULL + Automatic + User + + System.String + + System.String + + + None + force @@ -17359,6 +17376,23 @@ PS C:\> New-JCPolicy -TemplateName darwin_Login_Window_Text -Values $policyV None + + CascadeManager + + A SwitchParameter for Cascading the manager of the user to the users managed by the user. NULL, AUTOMATIC (bubble up), ID (prompt for manager ID) + + + NULL + Automatic + User + + System.String + + System.String + + + None + force @@ -17386,6 +17420,18 @@ PS C:\> New-JCPolicy -TemplateName darwin_Login_Window_Text -Values $policyV False + + CascadeManager + + A SwitchParameter for Cascading the manager of the user to the users managed by the user. NULL, AUTOMATIC (bubble up), ID (prompt for manager ID) + + System.String + + System.String + + + None + force @@ -17465,7 +17511,28 @@ PS C:\> New-JCPolicy -TemplateName darwin_Login_Window_Text -Values $policyV -------------------------- Example 2 -------------------------- PS C:\> Remove-JCUser cclemons -Force - Removes the JumpCloud User with Username 'cclemons' using the -Force Parameter. A warning message will not be presented to confirm this operation. + Removes the JumpCloud User with Username 'cclemons' using the -Force Parameter. A warning message will not be presented to confirm this operation. If the cclemons is a manager of other users, the `Force` parameter will clear cclemons' subordinates `manager` field. In other words if a user is managed by cclemons, removing cclemons will also remove that user's manager field in JumpCloud. + + + + -------------------------- Example 3 -------------------------- + PS C:\> Remove-JCUser cclemons -CascadeManager null + + Removes the Jumpcloud user with Username 'cclemons'. If `cclemons` manages other JumpCloud users, those user's will have their manager field set to null. Note. This command as the same effect as running `Remove-JCUser cclemons -Force` + + + + -------------------------- Example 4 -------------------------- + PS C:\> Remove-JCUser cclemons -CascadeManager automatic + + Removes the JumpCloud user with the username 'cclemons' and automatically update's their subordinates manager field to `cclemons` manager. Ex. If `cclemons` is a manager and is also managed by another user with username: `some.manager`, the users managed by `cclemons` will be reassigned to `some.manager` upon `cclemons` removal. If `cclemons` is not managed by anyone, the manager field for the `cclemons` subordinates will be set to null. + + + + -------------------------- Example 5 -------------------------- + PS C:\> Remove-JCUser cclemons -CascadeManager User -CascadeManagerUser some.manager + + Removes the JumpCloud user with the username `cclemons`. If `cclemons` is a manager, their subordinates will be reassigned to the manager specified by the provided username/id with CascadeManagerUser parameter. In this case, `cclemons` subordinates will be managed by the user with username: `some.manager` after `cclemons` is removed. diff --git a/PowerShell/ModuleChangelog.md b/PowerShell/ModuleChangelog.md index 8b48ff39c..f7b559322 100644 --- a/PowerShell/ModuleChangelog.md +++ b/PowerShell/ModuleChangelog.md @@ -1,18 +1,24 @@ ## 2.16.0 -Release Date: December 16, 2024 +Release Date: December 17, 2024 #### RELEASE NOTES ``` +This release introduces support for cascading managers with `Remove-JCUser` This release introduces a bug fix for `Import-JCUsersFromCSV` and `Update-JCUsersFromCSV` issues with importing more than 10 custom attributes. This release also introduces `Update-JCDeviceFromCSV` and `New-JCDeviceUpdateTemplate` functions. ``` #### FEATURES: + - Introduces the `Update-JCDeviceFromCSV` and `New-JCDeviceUpdateTemplate` functions - `New-JCDeviceUpdateTemplate`: Creates a csv template used for bulk updating devices - `Update-JCDeviceFromCSV`: Updates a list of devices from a CSV created by the `New-JCDeviceUpdateTemplate` function - +- Introduces `Remove-JCUser` - Added -CascadeManager (null, automatic, user) parameter + - null - Manager field for managed users by the user being removed will be set to null + - automatic - If the user (manager1) being removed is a manager but also managed by another user(manager2). The manager for managed users will cascade to manager2. + - User - Manually specify the manager for users managed by the user/manager being removed + - -CascadeManagerUser Id/Username #### BUG FIXES: - Fixed a bug with `Import-JCUsersFromCSV` and `Update-JCUsersFromCSV` throwing error when importing 10 or more Custom Attributes due to a sorting issue diff --git a/scripts/windows/UninstallWindowsAgent.ps1 b/scripts/windows/UninstallWindowsAgent.ps1 index c7cc58fcd..5b8aca6f4 100644 --- a/scripts/windows/UninstallWindowsAgent.ps1 +++ b/scripts/windows/UninstallWindowsAgent.ps1 @@ -1,5 +1,5 @@ - # Function to check if running as Administrator - function Test-Administrator { +# Function to check if running as Administrator +function Test-Administrator { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) @@ -13,14 +13,75 @@ if (-not (Test-Administrator)) { # Create HKCR Mapping New-PSDrive -Name "HKCR" -PSProvider Registry -Root "HKEY_CLASSES_ROOT" -ErrorAction SilentlyContinue +function Get-UninstallExeCommand($uninstallString) { + $index = $uninstallString.IndexOf("`"", 0) + $index = $uninstallString.IndexOf("`"", $index + 1) + + $cmd = $uninstallString.SubString(1, $index - 1) + $arguments = $uninstallString.SubString($index + 1).Trim() + + return @{ + Cmd = $cmd + Arguments = $arguments + Key = "" + } +} + +function Get-UninstallMsiCommand($productCode) { + $cmd = "msiexec.exe" + $arguments = "/x `"$productCode`" /qn /l*v `"$env:SystemRoot\temp\jcagentforceuninstall.log`"" + + return @{ + Cmd = $cmd + Arguments = $arguments + Key = "" + } +} + +function Find-UninstallCommands($uninstallKey) { + $uninstallCommands = @() + + Get-ChildItem -Path $uninstallKey | ForEach-Object { + try { + $displayName = (Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue).DisplayName + if ($displayName -like "*JumpCloud Agent*") { + $uninstallString = (Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue).UninstallString + if ($uninstallString -like "MsiExec.exe*") { + $command = Get-UninstallMsiCommand $_.PSChildName + $command.Key = "$uninstallKey/$($_.PSChildName)" + $uninstallCommands += $command + } + } + + # if uninstall agent, remote assist or tray apps will not work anymore so, unisntall them also + # jumpcloud tray is uninstalled by the MSI installer so there is not separate installer. + if ($displayName -like "*JumpCloud Remote Assist*" -or $displayName -like "*jumpcloud-agent-app*") { + $uninstallString = (Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue).QuietUninstallString + $command = Get-UninstallExeCommand $uninstallString + $command.Key = "$uninstallKey/$($_.PSChildName)" + $uninstallCommands += $command + } + } catch { + Write-Host "Error accessing $($_.PSPath)" + } + } + + return @{ Commands = $uninstallCommands } +} + # Function to recursively search for DisplayName and ProductName in the registry function Find-JumpCloudGUID { $rootKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products" $uninstallKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" + $uninstallWowKey = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" $classesKey = "HKCR:\Installer\Products" - + $agentGUIDs = @() $msiGUIDs = @() + $uninstallCommands = @() + + (Find-UninstallCommands $uninstallKey).Commands | ForEach-Object { $uninstallCommands += $_ } + (Find-UninstallCommands $uninstallWowKey).Commands | ForEach-Object { $uninstallCommands += $_ } # Searching Installer Products for JumpCloud Agent GUIDs Get-ChildItem -Path $rootKey | ForEach-Object { @@ -62,22 +123,47 @@ function Find-JumpCloudGUID { } } - return @{ AgentGUIDs = $agentGUIDs; MSIGUIDs = $msiGUIDs } + return @{ AgentGUIDs = $agentGUIDs; MSIGUIDs = $msiGUIDs; Uninstalls = $uninstallCommands } +} + +function Remove-Folder($folder) { + if (Test-Path $folder) { + Remove-Item -Path $folder -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Successfully deleted folder: $folder" + } } # Function to remove registry keys, folder, and service function Remove-JumpCloud { $guids = Find-JumpCloudGUID - + # Flag to track if items to remove were found - $foundItemsToRemove = $false - + $foundItemsToRemove = $false + + # if the MSI was used to install, we want to uninstall that route first + foreach ($uninst in $guids.Uninstalls) { + if (-not(Test-Path -Path $uninst.Key)) { + Write-Host "$($uninst.Key) already removed" + continue; + } + + $foundItemsToRemove = $true + Write-Output "Uninstallation of $($uninst.Cmd) $($uninst.Arguments) started." + + # Uninstall the MSI package, PS will fail without quotes around product code + # puposely give uninstall log a different name to differentiate + Start-Process $uninst.Cmd -ArgumentList $uninst.Arguments -Wait + + Write-Output "Uninstallation of $($uninst.Cmd) $($uninst.Arguments) completed." + } + + # remove installation registry keys if left behind. foreach ($guid in $guids.AgentGUIDs) { # Removing registry keys $installerKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$guid" $classesKey = "HKCR:\Installer\Products\$guid" $jumpcloudSoftwareKey = "HKLM:\Software\JumpCloud" - + foreach ($msiGuid in $guids.MSIGUIDs) { $uninstallKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$msiGuid" @@ -91,25 +177,56 @@ function Remove-JumpCloud { } } } + } - # Stopping and removing the JumpCloud Agent service - $serviceName = "jumpcloud-agent" - if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { - $foundItemsToRemove = $true - Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue - Start-Sleep -Seconds 6 - sc.exe delete $serviceName - Start-Sleep -Seconds 6 - Write-Host "Service $serviceName successfully removed." - } - - # Removing the JumpCloudfolder - $jumpcloudFolder = "C:\Program Files\JumpCloud" - if (Test-Path $jumpcloudFolder) { - $foundItemsToRemove = $true - Remove-Item -Path $jumpcloudFolder -Recurse -Force -ErrorAction SilentlyContinue - Write-Host "Successfully deleted folder: $jumpcloudFolder" - } + # Stopping and removing the JumpCloud Agent service if exists + $serviceName = "jumpcloud-agent" + if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { + $foundItemsToRemove = $true + Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 6 + sc.exe delete $serviceName + Start-Sleep -Seconds 6 + Write-Host "Service $serviceName successfully removed." + } + + # stop and remove jumpcloud try if it exists + Get-Process -Name jumpcloudtray -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "stopping tray process $($_.Id)" + Stop-Process -id $_.Id -ErrorAction SilentlyContinue + } + + # Removing the JumpCloudfolder + $folder = "$($env:ProgramFiles)\JumpCloud" + Remove-Folder $folder + + # remove tray folder + $folder = "$($env:ProgramFiles)\JumpCloudTray" + Remove-Folder $folder + + $folder = "$env:APPDATA\JumpCloud" + Remove-Folder $folder + + # remove older credential provider files if they exists in sys dir + if (Test-Path "$env:SystemRoot\System32\JumpCloud*.dll") { + $foundItemsToRemove = $true + Remove-Item -Path "$env:SystemRoot\System32\JumpCloud*.dll" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Successfully deleted files from system32" + } + + # remove windows runs on startups + $runKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" + + # Get the registry key + (Get-Item -Path $runKeyPath).Property | ForEach-Object { + try { + if ($_ -like "*jumpcloud-*") { + Write-Host "Delete $runKeyPath $_" + Remove-ItemProperty -Path $runKeyPath -Name $_ + } + } catch { + Write-Host "Error accessing $($_)" + } } # Check if nothing was found to remove