From cc8907b0d1117e046be500fad11c6c48c98f171a Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 7 Jan 2025 15:21:30 -0700 Subject: [PATCH 01/73] copied functions into new directory --- .../Powershell/Private/Add-NativeMethod.ps1 | 22 ++++ .../Private/Backup-RegistryHive.ps1 | 41 ++++++ .../Private/Close-ProcessByOwner.ps1 | 57 +++++++++ .../Private/Convert-SecurityIdentifier.ps1 | 15 +++ .../Powershell/Private/Convert-UserName.ps1 | 15 +++ .../Get-ProtocolTypeAssociation.ps1 | 48 +++++++ .../Get-UserFileTypeAssociation.ps1 | 50 ++++++++ .../Powershell/Private/Get-DomainStatus.ps1 | 13 ++ .../Private/Get-MtpOrganization.ps1 | 74 +++++++++++ .../Powershell/Private/Get-NetBiosName.ps1 | 24 ++++ .../Powershell/Private/Get-ProcessByOwner.ps1 | 64 ++++++++++ .../Private/Get-ProfileImagePath.ps1 | 17 +++ .../Powershell/Private/Get-ProfileSize.ps1 | 13 ++ .../Private/Get-RegistryExeStatus.ps1 | 23 ++++ .../Private/Get-SecurityIdentifier.ps1 | 6 + .../Powershell/Private/Get-WindowsDrive.ps1 | 4 + .../Private/Install-JumpCloudAgent.ps1 | 79 ++++++++++++ .../Private/New-LocalUserProfile.ps1 | 62 +++++++++ .../Private/Register-NativeMethod.ps1 | 23 ++++ .../Private/RegistryKey/New-RegKey.ps1 | 8 ++ .../Private/RegistryKey/Set-ValueToKey.ps1 | 11 ++ .../Powershell/Private/Remove-ItemIfExist.ps1 | 17 +++ .../Private/Remove-LocalUserProfile.ps1 | 61 +++++++++ .../Private/Restart-ComputerWithDelay.ps1 | 26 ++++ .../Private/Set-ADMUScheduledTask.ps1 | 36 ++++++ .../Powershell/Private/Set-FileAttribute.ps1 | 50 ++++++++ .../Private/Set-JCUserToSystemAssociation.ps1 | 66 ++++++++++ .../Powershell/Private/Set-RegistryExe.ps1 | 47 +++++++ .../Private/Set-UserRegistryLoadState.ps1 | 119 ++++++++++++++++++ .../Private/Show-ProcessListResult.ps1 | 30 +++++ .../Powershell/Private/Start-NewProcess.ps1 | 11 ++ .../Powershell/Private/Test-CharLen.ps1 | 18 +++ .../Private/Test-DATFilePermission.ps1 | 98 +++++++++++++++ .../Powershell/Private/Test-FileAttribute.ps1 | 30 +++++ .../Powershell/Private/Test-HasNoSpace.ps1 | 7 ++ .../Powershell/Private/Test-IsNotEmpty.ps1 | 7 ++ .../Private/Test-JumpCloudSystemKey.ps1 | 21 ++++ .../Private/Test-JumpCloudUsername.ps1 | 76 +++++++++++ .../Powershell/Private/Test-LocalUserName.ps1 | 29 +++++ .../Private/Test-RegistryValueMatch.ps1 | 34 +++++ .../Private/Test-UserFolderRedirect.ps1 | 67 ++++++++++ .../Private/Test-UserRegistryLoadState.ps1 | 74 +++++++++++ .../Powershell/Private/Test-UsernameOrSid.ps1 | 48 +++++++ .../Powershell/Private/Uninstall-Program.ps1 | 19 +++ .../Private/Write-AdmuErrorMessage.ps1 | 46 +++++++ .../Powershell/Private/Write-ToLog.ps1 | 106 ++++++++++++++++ .../Powershell/Private/Write-ToProgress.ps1 | 67 ++++++++++ .../Powershell/Tests/Functions.Tests.ps1 | 42 +------ 48 files changed, 1884 insertions(+), 37 deletions(-) create mode 100644 jumpcloud-ADMU/Powershell/Private/Add-NativeMethod.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Convert-SecurityIdentifier.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Convert-UserName.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-ProtocolTypeAssociation.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-UserFileTypeAssociation.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-ProcessByOwner.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-ProfileImagePath.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-ProfileSize.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-RegistryExeStatus.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-SecurityIdentifier.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-WindowsDrive.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Install-JumpCloudAgent.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/New-LocalUserProfile.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Register-NativeMethod.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/RegistryKey/New-RegKey.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/RegistryKey/Set-ValueToKey.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Remove-ItemIfExist.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Restart-ComputerWithDelay.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Set-ADMUScheduledTask.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Set-FileAttribute.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Set-RegistryExe.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Show-ProcessListResult.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Start-NewProcess.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-CharLen.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-DATFilePermission.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-FileAttribute.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-HasNoSpace.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-IsNotEmpty.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-JumpCloudSystemKey.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-JumpCloudUsername.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-UserRegistryLoadState.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Uninstall-Program.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 create mode 100644 jumpcloud-ADMU/Powershell/Private/Write-ToProgress.ps1 diff --git a/jumpcloud-ADMU/Powershell/Private/Add-NativeMethod.ps1 b/jumpcloud-ADMU/Powershell/Private/Add-NativeMethod.ps1 new file mode 100644 index 00000000..4c780f48 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Add-NativeMethod.ps1 @@ -0,0 +1,22 @@ +function Add-NativeMethod { + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param($typeName = 'NativeMethods') + + process { + $nativeMethodsCode = $script:nativeMethods | ForEach-Object { " + [DllImport(`"$($_.Dll)`")] + public static extern $($_.Signature); + " } + + Add-Type @" + using System; + using System.Text; + using System.Runtime.InteropServices; + public static class $typeName { + $nativeMethodsCode + } +"@ + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 b/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 new file mode 100644 index 00000000..e6f05a08 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 @@ -0,0 +1,41 @@ +Function Backup-RegistryHive { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.String] + $profileImagePath, + # Parameter help description + [Parameter(Mandatory = $true)] + [System.String] + $SID + ) + begin { + # get sid from PIP: + $domainUsername = Convert-SecurityIdentifier -Sid $SID + } + process { + try { + Copy-Item -Path "$profileImagePath\NTUSER.DAT" -Destination "$profileImagePath\NTUSER.DAT.BAK" -ErrorAction Stop + Copy-Item -Path "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat" -Destination "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" -ErrorAction Stop + } catch { + $processList = Get-ProcessByOwner -username $domainUsername + if ($processList) { + Show-ProcessListResult -ProcessList $processList -domainUsername $domainUsername + # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + } + try { + Write-ToLog -Message("Initial backup was not successful, trying again...") + Write-ToLog $CloseResults + Start-Sleep 1 + # retry: + Copy-Item -Path "$profileImagePath\NTUSER.DAT" -Destination "$profileImagePath\NTUSER.DAT.BAK" -ErrorAction Stop + Copy-Item -Path "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat" -Destination "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" -ErrorAction Stop + } catch { + Write-ToLog -Message("Could Not Backup Registry Hives in $($profileImagePath): Exiting...") + Write-AdmuErrorMessage -Error:("backup_error") + Write-ToLog -Message($_.Exception.Message) + throw "Could Not Backup Registry Hives in $($profileImagePath): Exiting..." + } + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 b/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 new file mode 100644 index 00000000..00060fe5 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 @@ -0,0 +1,57 @@ +function Close-ProcessByOwner { + [CmdletBinding()] + param ( + # Parameter help description + [Parameter(Mandatory = $true)] + [System.Object] + $ProcesssList, + # force close processes + [Parameter()] + [bool] + $force + ) + + begin { + $resultList = New-Object System.Collections.ArrayList + } + + process { + switch ($force) { + $true { + foreach ($item in $ProcesssList) { + Write-ToLog "Attempting to close processID: $($item.ProcessId)" + $tkStatus = taskkill /t /f /PID $item.ProcessId 2>&1 + $tkSuccess = if ($tkStatus -match "ERROR") { + $false + } else { + $true + } + $resultList.Add( + [PSCustomObject]@{ + ProcessName = $item.ProcessName + ProcessID = $item.ProcessId + Closed = $tkSuccess + } + ) | Out-Null + } + } + $false { + foreach ($item in $ProcesssList) { + $resultList.Add( + [PSCustomObject]@{ + ProcessName = $item.ProcessName + ProcessID = $item.ProcessId + Closed = "NA" + } + ) | Out-Null + } + } + } + + # TODO: wait 1 -5 sec to ensure NTUser is closed + Start-Sleep 1 + } + end { + return $resultList + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Convert-SecurityIdentifier.ps1 b/jumpcloud-ADMU/Powershell/Private/Convert-SecurityIdentifier.ps1 new file mode 100644 index 00000000..75b51a98 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Convert-SecurityIdentifier.ps1 @@ -0,0 +1,15 @@ +function Convert-SecurityIdentifier { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + $Sid + ) + process { + try { + (New-Object System.Security.Principal.SecurityIdentifier($Sid)).Translate( [System.Security.Principal.NTAccount]).Value + } catch { + return $Sid + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Convert-UserName.ps1 b/jumpcloud-ADMU/Powershell/Private/Convert-UserName.ps1 new file mode 100644 index 00000000..0feeb968 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Convert-UserName.ps1 @@ -0,0 +1,15 @@ +function Convert-UserName { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + $user + ) + process { + try { + (New-Object System.Security.Principal.NTAccount($user)).Translate( [System.Security.Principal.SecurityIdentifier]).Value + } catch { + return $user + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-ProtocolTypeAssociation.ps1 b/jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-ProtocolTypeAssociation.ps1 new file mode 100644 index 00000000..dc1a0dd2 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-ProtocolTypeAssociation.ps1 @@ -0,0 +1,48 @@ +# MIT License + +# Copyright © 2022, Danysys +# Modified by JumpCloud + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +function Get-ProtocolTypeAssociation { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The SID of the user to capture file type associations')] + [System.String] + $UserSid + ) + $manifestList = @() + + $pathRoot = "HKEY_USERS:\$($UserSid)_admu\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\" + if (Test-Path $pathRoot) { + Get-ChildItem $pathRoot* | + ForEach-Object { + + $progId = (Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue).ProgId + if ($progId) { + $manifestList += [PSCustomObject]@{ + extension = $_.PSChildName + programId = $progId + } + } + } + } + return $manifestList +} +##### END MIT License ##### diff --git a/jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-UserFileTypeAssociation.ps1 b/jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-UserFileTypeAssociation.ps1 new file mode 100644 index 00000000..db1a1ef0 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/FileAssociations/Get-UserFileTypeAssociation.ps1 @@ -0,0 +1,50 @@ + +# MIT License + +# Copyright © 2022, Danysys +# Modified by JumpCloud + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Get user file type associations/FTA +function Get-UserFileTypeAssociation { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The SID of the user to capture file type associations')] + [System.String] + $UserSid + ) + $manifestList = @() + # Test path for file type associations + $pathRoot = "HKEY_USERS:\$($UserSid)_admu\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\" + if (Test-Path $pathRoot) { + $exts = Get-ChildItem $pathRoot* + foreach ($ext in $exts) { + $indivExtension = $ext.PSChildName + $progId = (Get-ItemProperty "$($pathRoot)\$indivExtension\UserChoice" -ErrorAction SilentlyContinue).ProgId + $manifestList += [PSCustomObject]@{ + extension = $indivExtension + programId = $progId + } + } + } + return $manifestList +} + +##### END MIT License ##### diff --git a/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 new file mode 100644 index 00000000..4847e9da --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 @@ -0,0 +1,13 @@ +function Get-DomainStatus { + $ADStatus = dsregcmd.exe /status + foreach ($line in $ADStatus) { + if ($line -match "AzureADJoined : ") { + $AzureADStatus = ($line.trimstart('AzureADJoined : ')) + } + if ($line -match "DomainJoined : ") { + $LocalDomainStatus = ($line.trimstart('DomainJoined : ')) + } + } + # Return both statuses + return $AzureADStatus, $LocalDomainStatus +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 new file mode 100644 index 00000000..67853792 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 @@ -0,0 +1,74 @@ +function Get-mtpOrganization { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.String] + $apiKey, + [Parameter()] + [System.String] + $orgID, + [parameter()] + [switch] + $inputType + ) + begin { + $skip = 0 + $limit = 100 + $paginate = $true + $Headers = @{ + 'Content-Type' = 'application/json'; + 'Accept' = 'application/json'; + 'x-api-key' = "$($apiKey)"; + } + $results = @() + if ($orgID) { + Write-ToLog -Message "OrgID specified, attempting to validate org..." + $baseURl = "https://console.jumpcloud.com/api/organizations/$($orgID)" + $Request = Invoke-WebRequest -Uri "$($baseUrl)?limit=$($limit)&skip=$($skip)" -Method Get -Headers $Headers -UseBasicParsing + $Content = $Request.Content | ConvertFrom-Json + $results += $Content + } else { + Write-ToLog -Message "No OrgID specified, attempting to search for valid orgs..." + while ($paginate) { + $baseUrl = "https://console.jumpcloud.com/api/organizations" + $Request = Invoke-WebRequest -Uri "$($baseUrl)?limit=$($limit)&skip=$($skip)" -Method Get -Headers $Headers -UseBasicParsing + $Content = $Request.Content | ConvertFrom-Json + $results += $Content.results + if ($Content.results.Count -eq $limit) { + $skip += $limit + } else { + $paginate = $false + } + } + } + } + process { + # if there's only one org return found org, else prompt for selection + if (($results.count -eq 1) -And ($($results._id))) { + Write-ToLog -Message "API Key Validated`nOrgName: $($results.DisplayName)" + $orgs = $results._id, $results.DisplayName + } elseif (($results.count -gt 1)) { + Write-ToLog -Message "Found $($results.count) orgs with the specifed API Key" + # initial prompt for MTP selection + switch ($inputType) { + $true { + Write-ToLog -Message "Prompting for MTP Admin Selection" + $orgs = show-mtpSelection -Orgs $results + Write-ToLog -Message "API Key Validated`nOrgName: $($orgs[1])" + } + Default { + Write-ToLog -Message "API Key appears to be a MTP Admin Key. Please specify the JumpCloudOrgID Parameter and try again" + throw "API Key appears to be a MTP Admin Key. Please specify the JumpCloudOrgID Parameter and try again" + } + } + } else { + Write-ToLog -Message "No orgs matched provided API Key" + $orgs = $false + } + + } + end { + #returned org as an object [0]=id [1]=dispalyName + return $orgs + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 new file mode 100644 index 00000000..0f8bdb1d --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 @@ -0,0 +1,24 @@ +#TODO Add check if library installed on system, else don't import +Add-Type -MemberDefinition @" +[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] +public static extern uint NetApiBufferFree(IntPtr Buffer); +[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] +public static extern int NetGetJoinInformation( + string server, + out IntPtr NameBuffer, + out int BufferType); +"@ -Namespace Win32Api -Name NetApi32 + +function Get-NetBiosName { + $pNameBuffer = [IntPtr]::Zero + $joinStatus = 0 + $apiResult = [Win32Api.NetApi32]::NetGetJoinInformation( + $null, # lpServer + [Ref] $pNameBuffer, # lpNameBuffer + [Ref] $joinStatus # BufferType + ) + if ( $apiResult -eq 0 ) { + [Runtime.InteropServices.Marshal]::PtrToStringAuto($pNameBuffer) + [Void] [Win32Api.NetApi32]::NetApiBufferFree($pNameBuffer) + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-ProcessByOwner.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-ProcessByOwner.ps1 new file mode 100644 index 00000000..111c1123 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-ProcessByOwner.ps1 @@ -0,0 +1,64 @@ +function Get-ProcessByOwner { + [CmdletBinding()] + param ( + # the username to of which to search active processes + [Parameter(Mandatory = $true, ParameterSetName = "ByUsername")] + [System.String] + $username, + # the account security identifier of which to search processes + [Parameter(Mandatory = $true, ParameterSetName = "BySID")] + [System.String] + $SID + ) + + begin { + switch ($PSBoundParameters.Keys) { + 'username' { + # validate username + $accountSid = Convert-UserName -user $username + $domainUsername = Convert-SecurityIdentifier -Sid $accountSid + } + 'SID' { + # validate SID + $domainUsername = Convert-SecurityIdentifier -Sid $SID + } + } + } + process { + $processList = New-Object System.Collections.ArrayList + $processes = Get-Process + foreach ($process in $processes) { + if ($process.id) { + # TODO: processItem would throw a null value exception + $processItem = (Get-WmiObject -Class Win32_Process -Filter:("ProcessId = $($Process.Id)")) + if (![string]::IsNullOrEmpty($processItem)) { + # Create null value check for processItem + $owner = $processItem.GetOwner() + $processList.Add( + [PSCustomObject]@{ + ProcessName = if ($process.Name) { + $process.Name + } else { + "NA" + } + ProcessId = if ($process.Id) { + $process.Id + } else { + "NA" + } + Owner = "$($owner.Domain)\$($owner.User)" + } + ) | Out-Null + } + } + } + # Filter Process List by User: + $processList = $processList | Where-Object { $_.Owner -eq $domainUsername } + } + + end { + Write-ToLog -Message:("Getting Processes for: $domainUsername") + Write-ToLog -Message:("Processes found: $($processList.count)") + return $processList + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-ProfileImagePath.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-ProfileImagePath.ps1 new file mode 100644 index 00000000..ab61b96e --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-ProfileImagePath.ps1 @@ -0,0 +1,17 @@ + +Function Get-ProfileImagePath { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] + [System.String] + $UserSid + ) + $profileImagePath = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' + $UserSid) -Name 'ProfileImagePath' + if ([System.String]::IsNullOrEmpty($profileImagePath)) { + Write-ToLog -Message("Could not get the profile path for $UserSid exiting...") -level Warn + throw "Could not get the profile path for $UserSid exiting..." + } else { + return $profileImagePath + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-ProfileSize.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-ProfileSize.ps1 new file mode 100644 index 00000000..110eab1c --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-ProfileSize.ps1 @@ -0,0 +1,13 @@ +# Get Profile Size function +function Get-ProfileSize { + param ( + [Parameter(Mandatory = $true)] + [System.String] + $profilePath + ) + $files = Get-ChildItem -Path $profilePath -Recurse -Force | Where-Object { -not $_.PSIsContainer } | Measure-Object -Property Length -Sum + $profileSizeSum = $files.Sum + $totalSizeGB = [math]::round($profileSizeSum / 1GB, 1) + Write-ToLog -Message:("Profile Size: $totalSizeGB GB") + return $totalSizeGB +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-RegistryExeStatus.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-RegistryExeStatus.ps1 new file mode 100644 index 00000000..71db43c8 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-RegistryExeStatus.ps1 @@ -0,0 +1,23 @@ +function Get-RegistryExeStatus { + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter()] + [System.Object] + $resultsObject + ) + # if resultsObject has an exception, the command failed: + if ($resultsObject.Exception) { + # write the warning + Write-Warning "$($resultsObject.TargetObject)" + Write-Warning "$($resultsObject.InvocationInfo.PositionMessage)" + + # return false + $status = $false + } else { + # return true + $status = $true + } + # return true or false + return $status +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-SecurityIdentifier.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-SecurityIdentifier.ps1 new file mode 100644 index 00000000..b2b3d917 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-SecurityIdentifier.ps1 @@ -0,0 +1,6 @@ + +function Get-SecurityIdentifier ([string]$User) { + $objUser = New-Object System.Security.Principal.NTAccount($User) + $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) + $strSID.Value +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Get-WindowsDrive.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-WindowsDrive.ps1 new file mode 100644 index 00000000..3dc3dc95 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-WindowsDrive.ps1 @@ -0,0 +1,4 @@ +Function Get-WindowsDrive { + $drive = (Get-WmiObject Win32_OperatingSystem).SystemDrive + return $drive +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Install-JumpCloudAgent.ps1 b/jumpcloud-ADMU/Powershell/Private/Install-JumpCloudAgent.ps1 new file mode 100644 index 00000000..5342ab25 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Install-JumpCloudAgent.ps1 @@ -0,0 +1,79 @@ +Function Install-JumpCloudAgent( + [System.String]$AGENT_INSTALLER_URL + , [System.String]$AGENT_INSTALLER_PATH + , [System.String]$AGENT_PATH + , [System.String]$AGENT_BINARY_NAME + , [System.String]$AGENT_CONF_PATH + , [System.String]$JumpCloudConnectKey +) { + $AgentService = Get-Service -Name "jumpcloud-agent" -ErrorAction SilentlyContinue + If (!$AgentService) { + Write-ToLog -Message:('Downloading JCAgent Installer') -Level Verbose + #Download Installer + if ((Test-Path $AGENT_INSTALLER_PATH)) { + Write-ToLog -Message:('JumpCloud Agent Already Downloaded') -Level Verbose + } else { + (New-Object System.Net.WebClient).DownloadFile("${AGENT_INSTALLER_URL}", ($AGENT_INSTALLER_PATH)) + Write-ToLog -Message:('JumpCloud Agent Download Complete') -Level Verbose + } + Write-ToLog -Message:('Running JCAgent Installer') -Level Verbose + Write-ToLog -Message:("LogPath: $env:TEMP\jcUpdate.log") + # run .MSI installer + msiexec /i $AGENT_INSTALLER_PATH /quiet /L "$env:TEMP\jcUpdate.log" JCINSTALLERARGUMENTS=`"-k $($JumpCloudConnectKey) /VERYSILENT /NORESTART /NOCLOSEAPPLICATIONS`" + # perform installation checks: + for ($i = 0; $i -le 17; $i++) { + Write-ToLog -Message:('Waiting on JCAgent Installer...') + Start-Sleep -Seconds 30 + #Output the errors encountered + $AgentService = Get-Service -Name "jumpcloud-agent" -ErrorAction SilentlyContinue + if ($AgentService.Status -eq 'Running') { + Write-ToLog 'JumpCloud Agent Succesfully Installed' + $agentInstalled = $true + break + } + if (($i -eq 17) -and ($AgentService.Status -ne 'Running')) { + Write-ToLog -Message:('JCAgent did not install in the expected window') -Level Error + $agentInstalled = $false + } + } + + # wait on configuration file: + $config = get-content -Path $AGENT_CONF_PATH -ErrorAction Ignore + $regex = 'systemKey\":\"(\w+)\"' + $timeout = 0 + while ([system.string]::IsNullOrEmpty($config)) { + $config = get-content -Path $AGENT_CONF_PATH -ErrorAction Ignore + Write-ToLog -Message:('Waiting for JumpCloud agent config file...') + if ($timeout -eq 20) { + Write-ToLog -Message:('JCAgent could not register the system within the expected window') -Level Error + break + } + Start-Sleep 5 + $timeout += 1 + } + # If config continue to try to get SystemKey; else continue + if ($config) { + # wait on connect key + $systemKey = [regex]::Match($config, $regex).Groups[1].Value + $timeout = 0 + while ([system.string]::IsNullOrEmpty($systemKey)) { + $config = get-content -Path $AGENT_CONF_PATH + $systemKey = [regex]::Match($config, $regex).Groups[1].Value + Write-ToLog -Message:('Waiting for JumpCloud to register the local system...') + if ($timeout -eq 20) { + Write-ToLog -Message:('JCAgent could not register the system within the expected window') -Level Error + break + } + Start-Sleep 5 + $timeout += 1 + } + Write-ToLog -Message:("SystemKey Generated: $($systemKey)") + } + } + Write-ToLog -Message:("Is JumpCloud Agent Installed?: $($agentInstalled)") + if (($agentInstalled) -and (-not [system.string]::IsNullOrEmpty($systemKey)) ) { + Return $true + } else { + Return $false + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/New-LocalUserProfile.ps1 b/jumpcloud-ADMU/Powershell/Private/New-LocalUserProfile.ps1 new file mode 100644 index 00000000..75d929cf --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/New-LocalUserProfile.ps1 @@ -0,0 +1,62 @@ +function New-LocalUserProfile { + + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 0)] + [string]$UserName + ) + process { + $methodname = 'UserEnvCP2' + $script:nativeMethods = @(); + + if (-not ([System.Management.Automation.PSTypeName]$methodname).Type) { + Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,` + [MarshalAs(UnmanagedType.LPWStr)] string pszUserName,` + [Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)"; + + Add-NativeMethod -typeName $methodname; + } + + $sb = new-object System.Text.StringBuilder(260); + $pathLen = $sb.Capacity; + + Write-ToLog "Creating user profile for $UserName" -Level Verbose + if ($UserName -eq $env:computername) { + Write-ToLog "$UserName Matches ComputerName" -Level Verbose + $objUser = New-Object System.Security.Principal.NTAccount("$env:computername\$UserName") + } else { + $objUser = New-Object System.Security.Principal.NTAccount($UserName) + } + $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) + $SID = $strSID.Value + + try { + $result = [UserEnvCP2]::CreateProfile($SID, $Username, $sb, $pathLen) + if ($result -eq '-2147024713') { + $status = "$userName is an existing account" + Write-ToLog "$username creation result: $result" + } elseif ($result -eq '-2147024809') { + $status = "$username Not Found" + Write-ToLog "$username Creation Result: $result" + } elseif ($result -eq 0) { + $status = "$username Profile has been created" + Write-ToLog "$username Creation Result: $result" + } else { + $status = "$UserName unknown return result: $result" + } + } catch { + Write-Error $_.Exception.Message; + # break; + } + # $status + } + end { + return $SID + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Register-NativeMethod.ps1 b/jumpcloud-ADMU/Powershell/Private/Register-NativeMethod.ps1 new file mode 100644 index 00000000..9655aad0 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Register-NativeMethod.ps1 @@ -0,0 +1,23 @@ +function Register-NativeMethod { + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 0)] + [string]$dll, + + # Param2 help description + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 1)] + [string] + $methodSignature + ) + process { + $script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; } + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/RegistryKey/New-RegKey.ps1 b/jumpcloud-ADMU/Powershell/Private/RegistryKey/New-RegKey.ps1 new file mode 100644 index 00000000..c602880a --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/RegistryKey/New-RegKey.ps1 @@ -0,0 +1,8 @@ +# Reg Functions adapted from: +# https://social.technet.microsoft.com/Forums/windows/en-US/9f517a39-8dc8-49d3-82b3-96671e2b6f45/powershell-set-registry-key-owner-to-the-system-user-throws-error?forum=winserverpowershell + +function New-RegKey([string]$keyPath, [Microsoft.Win32.RegistryHive]$registryRoot) { + $Key = [Microsoft.Win32.Registry]::$registryRoot.CreateSubKey($keyPath) + Write-ToLog -Message:("Setting key at [KeyPath:$keyPath]") + $key.Close() +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/RegistryKey/Set-ValueToKey.ps1 b/jumpcloud-ADMU/Powershell/Private/RegistryKey/Set-ValueToKey.ps1 new file mode 100644 index 00000000..06c5273c --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/RegistryKey/Set-ValueToKey.ps1 @@ -0,0 +1,11 @@ +# Reg Functions adapted from: +# https://social.technet.microsoft.com/Forums/windows/en-US/9f517a39-8dc8-49d3-82b3-96671e2b6f45/powershell-set-registry-key-owner-to-the-system-user-throws-error?forum=winserverpowershell + +function Set-ValueToKey([Microsoft.Win32.RegistryHive]$registryRoot, [string]$keyPath, [string]$name, [System.Object]$value, [Microsoft.Win32.RegistryValueKind]$regValueKind) { + $regRights = [System.Security.AccessControl.RegistryRights]::SetValue + $permCheck = [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree + $Key = [Microsoft.Win32.Registry]::$registryRoot.OpenSubKey($keyPath, $permCheck, $regRights) + Write-ToLog -Message:("Setting value with properties [name:$name, value:$value, value type:$regValueKind]") + $Key.SetValue($name, $value, $regValueKind) + $key.Close() +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Remove-ItemIfExist.ps1 b/jumpcloud-ADMU/Powershell/Private/Remove-ItemIfExist.ps1 new file mode 100644 index 00000000..d182b714 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Remove-ItemIfExist.ps1 @@ -0,0 +1,17 @@ + +Function Remove-ItemIfExist { + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][String[]]$Path + , [Switch]$Recurse + ) + Process { + Try { + If (Test-Path -Path:($Path)) { + Remove-Item -Path:($Path) -Recurse:($Recurse) + } + } Catch { + Write-ToLog -Message ('Removal Of Temp Files & Folders Failed') -Level Warn + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 b/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 new file mode 100644 index 00000000..ba3ea21e --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 @@ -0,0 +1,61 @@ +function Remove-LocalUserProfile { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.String] + $UserName + ) + Begin { + # Validate that the user was just created by the ADMU + $removeUser = $false + $users = Get-LocalUser + foreach ($user in $users) { + # we only want to remove users with description "Created By JumpCloud ADMU" + if ( $user.name -match $UserName -And $user.description -eq "Created By JumpCloud ADMU" ) { + $UserSid = Get-SID -User $UserName + $UserPath = Get-ProfileImagePath -UserSid $UserSid + # Set RemoveUser bool to true + $removeUser = $true + } + } + if (!$removeUser) { + throw "Username match not found, not reversing" + } + } + Process { + # Remove the profile + if ($removeUser) { + # Remove the User + Remove-LocalUser -Name $UserName + # Remove the User Profile + if (Test-Path -Path $UserPath) { + $Group = New-Object System.Security.Principal.NTAccount("Builtin", "Administrators") + $ACL = Get-ACL $UserPath + $ACL.SetOwner($Group) + + Get-ChildItem $UserPath -Recurse -Force -errorAction SilentlyContinue | ForEach-Object { + Try { + Set-ACL -AclObject $ACL -Path $_.fullname -errorAction SilentlyContinue + } catch [System.Management.Automation.ItemNotFoundException] { + Write-Verbose 'ItemNotFound : $_' + } + } + # icacls $($UserPath) /grant administrators:F /T + # takeown /f $($UserPath) /r /d y + Remove-Item -Path $($UserPath) -Force -Recurse #-ErrorAction SilentlyContinue + } + # Remove the User SID + # TODO: if the profile SID is loaded in registry skip this and note in log + # Match the user SID + $matchedKey = get-childitem -path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' | Where-Object { $_.Name -match $UserSid } + # Set the Matched Key Path to PSPath so PowerShell can use the path + $matchedKeyPath = $($matchedKey.Name) -replace "HKEY_LOCAL_MACHINE", "HKLM:" + # Remove the UserSid Key from the ProfileList + Remove-Item -Path "$matchedKeyPath" -Recurse + } + } + End { + # Output some info + Write-ToLog -message:("$UserName's account, profile and Registry Key SID were removed") + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Restart-ComputerWithDelay.ps1 b/jumpcloud-ADMU/Powershell/Private/Restart-ComputerWithDelay.ps1 new file mode 100644 index 00000000..28cd0709 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Restart-ComputerWithDelay.ps1 @@ -0,0 +1,26 @@ +Function Restart-ComputerWithDelay { + Param( + [int]$TimeOut = 10 + ) + $continue = $true + + while ($continue) { + If ([console]::KeyAvailable) { + Write-Output "Restart Canceled by key press" + Exit; + } Else { + Write-Output "Press any key to cancel... restarting in $TimeOut" -NoNewLine + Start-Sleep -Seconds 1 + $TimeOut = $TimeOut - 1 + Clear-Host + If ($TimeOut -eq 0) { + $continue = $false + $Restart = $true + } + } + } + If ($Restart -eq $True) { + Write-Output "Restarting Computer..." + Restart-Computer -ComputerName $env:COMPUTERNAME -Force + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Set-ADMUScheduledTask.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-ADMUScheduledTask.ps1 new file mode 100644 index 00000000..195ae39c --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Set-ADMUScheduledTask.ps1 @@ -0,0 +1,36 @@ +function Set-ADMUScheduledTask { + # Param op "disable" or "enable" then -tasks (array of tasks) + param ( + [Parameter(Mandatory = $true)] + [ValidateSet("disable", "enable")] + [System.String] + $op, + [Parameter(Mandatory = $true)] + [System.Object[]] + $scheduledTasks + ) + + # Switch op + switch ($op) { + "disable" { + try { + $scheduledTasks | ForEach-Object { + Write-ToLog -message:("Disabling Scheduled Task: $($_.TaskName)") + Disable-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath | Out-Null + } + } catch { + Write-ToLog -message:("Failed to disable Scheduled Tasks $($_.Exception.Message)") + } + } + "enable" { + try { + $scheduledTasks | ForEach-Object { + Write-ToLog -message("Enabling Scheduled Task: $($_.TaskName)") + Enable-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath | Out-Null + } + } catch { + Write-ToLog -message("Could not enable Scheduled Task: $($_.TaskName)") -Level Warn + } + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Set-FileAttribute.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-FileAttribute.ps1 new file mode 100644 index 00000000..c8a3e9d2 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Set-FileAttribute.ps1 @@ -0,0 +1,50 @@ +function Set-FileAttribute { + [CmdletBinding()] + param ( + # Profile path + [Parameter(Mandatory = $true)] + [ValidateScript( { Test-Path $_ })] + [System.String] + $ProfilePath, + # Attribute to Remove + [Parameter(Mandatory = $true)] + [ValidateSet("ReadOnly", "Hidden", "System", "Archive", "Normal", "Temporary", "Offline")] + [System.String] + $Attribute, + # Operation verb (add/ remove) + [Parameter(Mandatory = $true)] + [ValidateSet( "Add", "Remove" )] + [System.String] + $Operation + ) + + begin { + $profilePropertiesBefore = Get-ItemProperty -Path $ProfilePath + $attributesBefore = $($profilePropertiesBefore.Attributes) + } + + process { + Write-ToLog "$profilePath attributes before: $($attributesBefore)" + # remove item with bitwise operators, keeping what was set but removing the $attribute + switch ($Operation) { + "Remove" { + $profilePropertiesBefore.Attributes = $profilePropertiesBefore.Attributes -band -bnot [System.IO.FileAttributes]::$Attribute + } + "Add" { + $profilePropertiesBefore.Attributes = $profilePropertiesBefore.Attributes -bxor [System.IO.FileAttributes]::$Attribute + } + } + $attributeTest = Test-FileAttribute -ProfilePath $ProfilePath -Attribute $Attribute + } + end { + $profilePropertiesAfter = Get-ItemProperty -Path $ProfilePath + $attributesAfter = $($profilePropertiesBefore.Attributes) + Write-ToLog "$profilePath attributes after: $($attributesAfter)" + + if ($attributeTest) { + return $true + } else { + return $false + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 new file mode 100644 index 00000000..b79615ba --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 @@ -0,0 +1,66 @@ +function Set-JCUserToSystemAssociation { + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$JcApiKey, + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][ValidateLength(24, 24)][string]$JcOrgId, + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$JcUserID, + [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][bool]$BindAsAdmin, + [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$UserAgent + ) + Begin { + $config = get-content "$WindowsDrive\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf" + $regex = 'systemKey\":\"(\w+)\"' + $systemKey = [regex]::Match($config, $regex).Groups[1].Value + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + If (!$systemKey) { + Write-ToLog -Message:("Could not find systemKey, aborting bind step") -Level:('Warn') + } + } + Process { + Write-ToLog -Message:("User matched in JumpCloud") + $Headers = @{ + 'Accept' = 'application/json'; + 'Content-Type' = 'application/json'; + 'x-api-key' = $JcApiKey; + 'x-org-id' = $JcOrgId; + } + $Form = @{ + 'op' = 'add'; + 'type' = 'system'; + 'id' = "$systemKey" + } + if ($BindAsAdmin) { + Write-ToLog -Message:("Bind As Admin specified. Setting sudo attributes for userID: $JcUserID") + $Form.Add("attributes", @{ + "sudo" = @{ + "enabled" = $true + "withoutPassword" = $false + } + } + ) + } else { + Write-ToLog -Message:("Bind As Admin NOT specified. userID: $JcUserID will be bound as a standard user") + } + $jsonForm = $Form | ConvertTo-Json + Try { + Write-ToLog -Message:("Attempting to bind userID: $JcUserID to systemID: $systemKey") + $Response = Invoke-WebRequest -Method 'Post' -Uri "https://console.jumpcloud.com/api/v2/users/$JcUserID/associations" -Headers $Headers -Body $jsonForm -UseBasicParsing -UserAgent $UserAgent + $StatusCode = $Response.StatusCode + } catch { + $errorMsg = $_.Exception.Message + $StatusCode = $_.Exception.Response.StatusCode.value__ + Write-ToLog -Message:("Could not bind user to system") -Level:('Warn') + } + + } + End { + # Associations post should return 204 success no content + if ($StatusCode -eq 204) { + Write-ToLog -Message:("Associations Endpoint returened statusCode $statusCode [success]") -Level:('Warn') + return $true + } else { + Write-ToLog -Message:("Associations Endpoint returened statusCode $statusCode | $errorMsg") -Level:('Warn') + return $false + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Set-RegistryExe.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-RegistryExe.ps1 new file mode 100644 index 00000000..21060f0c --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Set-RegistryExe.ps1 @@ -0,0 +1,47 @@ +function Set-RegistryExe { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateSet("Unload", "Load")] + [System.String]$op, + [ValidateSet("classes", "root")] + [System.String]$hive, + [Parameter(Mandatory = $true)] + [ValidateScript( { Test-Path $_ })] + [System.String]$ProfilePath, + # User Security Identifier + [Parameter(Mandatory = $true)] + [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] + [System.String]$UserSid + ) + begin { + switch ($hive) { + "classes" { + $key = "HKU\$($UserSid)_Classes_admu" + $hiveFile = "$ProfilePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" + } + "root" { + $key = "HKU\$($UserSid)_admu" + $hiveFile = "$ProfilePath\NTUSER.DAT.BAK" + } + } + } + process { + switch ($op) { + "Load" { + Write-ToLog "REG LOAD $KEY $hiveFile" + $results = REG LOAD $key $hiveFile *>&1 + } + "Unload" { + Write-ToLog "REG UNLOAD $KEY" + $results = REG UNLOAD $key *>&1 + } + } + $status = Get-RegistryExeStatus $results + } + end { + # Status here will be either true or false depending on whether or not the tool was able to perform the registry action requested + return $status + } + +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 new file mode 100644 index 00000000..d763171e --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 @@ -0,0 +1,119 @@ +function Set-UserRegistryLoadState { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateSet("Unload", "Load")] + [System.String]$op, + [Parameter(Mandatory = $true)] + [ValidateSet("classes", "root")] + [System.String]$hive, + [Parameter(Mandatory = $true)] + [ValidateScript( { Test-Path $_ })] + [System.String]$ProfilePath, + # User Security Identifier + [Parameter(Mandatory = $true)] + [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] + [System.String]$UserSid, + [Parameter()] + [System.Int32]$counter = 0 + ) + begin { + Write-ToLog -Message:("## Begin Registry $op $UserSid ##") + switch ($hive) { + "classes" { + $key = "HKU\$($UserSid)_Classes_admu" + $hiveFile = "$ProfilePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" + } + "root" { + $key = "HKU\$($UserSid)_admu" + $hiveFile = "$ProfilePath\NTUSER.DAT.BAK" + } + } + If ($counter -ge 0) { + $counter += 1 + } + if ($counter -gt 3) { + # if we've tried to close the hive three times, throw error + throw "Registry $op $key failed" + } + } + process { + $username = Convert-SecurityIdentifier $UserSid + switch ($op) { + "Load" { + switch ($hive) { + "root" { + [gc]::collect() + $results = Set-RegistryExe -op Load -hive root -UserSid $UserSid -ProfilePath $ProfilePath + if ($results) { + Write-ToLog "Load Successful $results" + } else { + $processList = Get-ProcessByOwner -username $username + if ($processList) { + Show-ProcessListResult -ProcessList $processList -domainUsername $username + # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + } + Set-UserRegistryLoadstate -op Load -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive root + } + } + "classes" { + [gc]::collect() + $results = Set-RegistryExe -op Load -hive classes -UserSid $UserSid -ProfilePath $ProfilePath + if ($results) { + Write-ToLog "Load Successful $results" + } else { + $processList = Get-ProcessByOwner -username $username + if ($processList) { + Show-ProcessListResult -ProcessList $processList -domainUsername $username + # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + } + Set-UserRegistryLoadstate -op Load -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive classes + } + } + } + + + } + "Unload" { + switch ($hive) { + "root" { + [gc]::collect() + + $results = Set-RegistryExe -op Unload -hive root -UserSid $UserSid -ProfilePath $ProfilePath + if ($results) { + Write-ToLog "Unload Successful $results" + + } else { + $processList = Get-ProcessByOwner -username $username + if ($processList) { + Show-ProcessListResult -ProcessList $processList -domainUsername $username + # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + } + Set-UserRegistryLoadstate -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive root + } + } + "classes" { + [gc]::collect() + + $results = Set-RegistryExe -op Unload -hive classes -UserSid $UserSid -ProfilePath $ProfilePath + if ($results) { + Write-ToLog "Unload Successful $results" + + } else { + $processList = Get-ProcessByOwner -username $username + if ($processList) { + Show-ProcessListResult -ProcessList $processList -domainUsername $username + # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + } + Set-UserRegistryLoadstate -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive classes + } + } + } + } + } + } + end { + Write-ToLog -Message:("## End Registry $op $UserSid ##") + + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Show-ProcessListResult.ps1 b/jumpcloud-ADMU/Powershell/Private/Show-ProcessListResult.ps1 new file mode 100644 index 00000000..69029f40 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Show-ProcessListResult.ps1 @@ -0,0 +1,30 @@ +function Show-ProcessListResult { + [CmdletBinding()] + param ( + # processList from Get-ProcessByOwner + [Parameter(Mandatory = $true)] + [System.Object] + $ProcessList, + # domainUsername from Get-ProcessByOwner + [Parameter(Mandatory = $true)] + [System.String] + $domainUsername + ) + + begin { + if (-not $ProcessList) { + Write-ToLog -Message:("No system processes were found for $domainUsername") + return + } else { + Write-ToLog -Message:("$($ProcessList.count) processes were found for $domainUsername") + } + } + + process { + Write-ToLog "The following processes were found running under $domainUsername's account" + foreach ($process in $ProcessList) { + Write-ToLog -Message:("ProcessName: $($Process.ProcessName) | ProcessId: $($Process.ProcessId)") + } + } + # TODO: Get Processes not owned by user: i.e. search open handles in memory that have been accessed by file +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Start-NewProcess.ps1 b/jumpcloud-ADMU/Powershell/Private/Start-NewProcess.ps1 new file mode 100644 index 00000000..a38503a6 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Start-NewProcess.ps1 @@ -0,0 +1,11 @@ +#Start process and wait then close after 5mins +Function Start-NewProcess([string]$pfile, [string]$arguments, [int32]$Timeout = 300000) { + $p = New-Object System.Diagnostics.Process; + $p.StartInfo.FileName = $pfile; + $p.StartInfo.Arguments = $arguments + [void]$p.Start(); + If (! $p.WaitForExit($Timeout)) { + Write-ToLog -Message "Windows ADK Setup did not complete after 5mins"; + Get-Process | Where-Object { $_.Name -like "adksetup*" } | Stop-Process + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-CharLen.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-CharLen.ps1 new file mode 100644 index 00000000..9a3e4dcb --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-CharLen.ps1 @@ -0,0 +1,18 @@ +Function Test-CharLen { + [CmdletBinding()] + param ( + # Char Length to test + [Parameter(Mandatory = $true)] + [System.Int32] + $len, + # String to test #allow false to allow for searching empty strings + [Parameter(Mandatory = $false)] + [System.String] + $testString + ) + If ($testString.Length -eq $len) { + Return $true + } Else { + Return $false + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Test-DATFilePermission.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-DATFilePermission.ps1 new file mode 100644 index 00000000..7d4b1dc5 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-DATFilePermission.ps1 @@ -0,0 +1,98 @@ +# Function to validate if NTUser.dat has SYSTEM, Administrators, and the specified user as full control +function Test-DATFilePermission { + param ( + [Parameter(Mandatory = $true)] + [System.String] + $path, + [Parameter(Mandatory = $true)] + [System.String] + $username, + [Parameter(Mandatory = $true)] + [ValidateSet("registry", "ntfs")] + [System.String] + $type + + ) + begin { + $aclUser = "$($Env:ComputerName)\$username" + # ACL naming differs on registry/ ntfs file system, set the correct type + switch ($type) { + 'registry' { + $FilePermissionType = 'RegistryRights' + } + 'ntfs' { + $FilePermissionType = 'FileSystemRights' + } + } + # define empty list + $permissionsHash = @{} + # define required list to test + $requiredAccess = @{ + "NT AUTHORITY\SYSTEM" = @{ + name = "System" + }; + "BUILTIN\Administrators" = @{ + name = "Administrators" + }; + "$($aclUser)" = @{ + name = "$username" + } + } + # Get the path + $ACL = Get-Acl $path + } + process { + # Using AccessControlType to check if it's a deny rule instead of allow since, with NTFS permissions, even if a user/admin is denied, there will still be an allow rule for them and not null + foreach ($requiredRule in $requiredAccess.keys) { + # foreach ($requiredRule in $systemRule, $administratorsRule, $specifiedUserRule) { + # write-ToLog "Begin testing: $($requiredRule)" + $FileACLs = $acl.Access | Where-Object { $_.IdentityReference -eq "$($requiredRule)" } + # write-ToLog "$($requiredRule) access count: $($FileACLs.Count)" + foreach ($fileACL in $FileACLs) { + $rulePermissions = [PSCustomObject]@{ + access = $FileACL.AccessControlType + permissionType = $FileACL.$($FilePermissionType) + identityReference = $FileACL.IdentityReference + ValidPermissions = $true + } + # There will sometimes be multiple FileACLs if an identity is denied access, in which case just break + if ($FileACL.AccessControlType -contains 'Deny') { + $rulePermissions.ValidPermissions = $false + $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null + break + } + # if fullControl access is not grated, just break + if ($FileACL.$($FilePermissionType) -notcontains 'FullControl') { + $rulePermissions.ValidPermissions = $false + $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null + break + } + # else record the access rule and assume it's valid + if ("$($requiredAccess["$($requiredRule)"].name)" -notin $permissionsHash.Keys) { + $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null + } + } + # if the access is not explicitly granted, record the missing value so we can make use of it later + if (-not $FileACLs) { + $rulePermissions = [PSCustomObject]@{ + access = $null + permissionType = $null + identityReference = $requiredRule + ValidPermissions = $false + } + if ("$($requiredAccess["$($requiredRule)"].name)" -notin $permissionsHash.Keys) { + $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null + } + } + } + + } + end { + # if the validPermission block contains any 'false' entries, return false + values, else return true + values + if (($permissionsHash.Values.ValidPermissions -contains $false)) { + return $false, $permissionsHash.Values + } else { + return $true, $permissionsHash.Values + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-FileAttribute.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-FileAttribute.ps1 new file mode 100644 index 00000000..e1f61856 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-FileAttribute.ps1 @@ -0,0 +1,30 @@ +function Test-FileAttribute { + [CmdletBinding()] + param ( + # Profile path + [Parameter(Mandatory = $true)] + [ValidateScript( { Test-Path $_ })] + [System.String]$ProfilePath, + # Attribute to Test + [Parameter(Mandatory = $true)] + [ValidateSet("ReadOnly", "Hidden", "System", "Archive", "Normal", "Temporary", "Offline")] + [System.String] + $Attribute + ) + + begin { + $profileProperties = Get-ItemProperty -Path $ProfilePath + } + + process { + $attributes = $($profileProperties.Attributes) + } + + end { + if ($attributes -match $Attribute) { + return $true + } else { + return $false + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-HasNoSpace.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-HasNoSpace.ps1 new file mode 100644 index 00000000..0d388fd5 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-HasNoSpace.ps1 @@ -0,0 +1,7 @@ +Function Test-HasNoSpace ([System.String] $field) { + If ($field -like "* *") { + Return $false + } Else { + Return $true + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-IsNotEmpty.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-IsNotEmpty.ps1 new file mode 100644 index 00000000..5c901c0d --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-IsNotEmpty.ps1 @@ -0,0 +1,7 @@ +Function Test-IsNotEmpty ([System.String] $field) { + If (([System.String]::IsNullOrEmpty($field))) { + Return $true + } Else { + Return $false + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-JumpCloudSystemKey.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-JumpCloudSystemKey.ps1 new file mode 100644 index 00000000..23e28a48 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-JumpCloudSystemKey.ps1 @@ -0,0 +1,21 @@ +function Test-JumpCloudSystemKey { + [CmdletBinding()] + [OutputType([System.Boolean])] + param ( + [Parameter()] + [System.String] + $WindowsDrive + ) + + process { + $config = get-content "$WindowsDrive\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf" -ErrorVariable configExitCode -ErrorAction SilentlyContinue + if ($configExitCode) { + $message += "JumpCloud Agent is not installed on this system`nPlease also enter your Connect Key to install JumpCloud" + $wshell = New-Object -ComObject Wscript.Shell + $var = $wshell.Popup("$message", 0, "ADMU Status", 0x0 + 0x40) + return $false + } else { + return $true + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-JumpCloudUsername.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-JumpCloudUsername.ps1 new file mode 100644 index 00000000..d0c2e90f --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-JumpCloudUsername.ps1 @@ -0,0 +1,76 @@ +function Test-JumpCloudUsername { + [CmdletBinding()] + [OutputType([System.Boolean])] + [OutputType([System.Object[]])] + param ( + [Parameter()] + [System.String] + $JumpCloudApiKey, + [Parameter()] + [System.String] + $JumpCloudOrgID, + [Parameter()] + [System.String] + $Username, + [Parameter()] + [System.Boolean] + $prompt = $false + ) + Begin { + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $Headers = @{ + 'Accept' = 'application/json'; + 'Content-Type' = 'application/json'; + 'x-api-key' = $JumpCloudApiKey; + 'x-org-id' = $JumpCloudOrgID; + } + + $Form = @{ + "filter" = @{ + 'and' = @( + @{'username' = @{'$regex' = "(?i)(`^$($Username)`$)" } } + ) + } + "fields" = "username , systemUsername" + } + $Body = $Form | ConvertTo-Json -Depth 4 + } + Process { + Try { + # Write-ToLog "Searching JC for: $Username" + $Response = Invoke-WebRequest -Method 'Post' -Uri "https://console.jumpcloud.com/api/search/systemusers" -Headers $Headers -Body $Body -UseBasicParsing + $Results = $Response.Content | ConvertFrom-Json + + $StatusCode = $Response.StatusCode + } catch { + $StatusCode = $_.Exception.Response.StatusCode.value__ + Write-ToLog -Message "Status Code $($StatusCode)" + } + } + End { + # Search User should return 200 success + If ($StatusCode -ne 200) { + Write-ToLog -Message "JumpCloud username could not be found" + Return $false, $null, $null + } + If ($Results.totalCount -eq 1 -and $($Results.results[0].username) -eq $Username) { + # write-host $Results.results[0]._id + Write-ToLog -Message "Identified JumpCloud User`nUsername: $($Results.results[0].username)`nID: $($Results.results[0]._id)" + if ($Results.results[0].SystemUsername) { + Write-ToLog -Message "JumpCloud User have a Local Account User set: $($Results.results[0].SystemUsername)" + return $true, $Results.results[0]._id, $Results.results[0].SystemUsername + } else { + return $true, $Results.results[0]._id, $null + } + + + } else { + if ($prompt) { + $message += "$Username is not a valid JumpCloud User`nPlease enter a valid JumpCloud Username" + $wshell = New-Object -ComObject Wscript.Shell + $var = $wshell.Popup("$message", 0, "ADMU Status", 0x0 + 0x40) + } + Return $false, $null, $null + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 new file mode 100644 index 00000000..a0a9195e --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 @@ -0,0 +1,29 @@ +function Test-Localusername { + [CmdletBinding()] + param ( + [system.array] $field + ) + begin { + $win32UserProfiles = Get-WmiObject -Class:('Win32_UserProfile') -Property * | Where-Object { $_.Special -eq $false } + $users = $win32UserProfiles | Select-Object -ExpandProperty "SID" | Convert-SecurityIdentifier + $localusers = new-object system.collections.arraylist + foreach ($username in $users) { + $domain = ($username -split '\\')[0] + if ($domain -match $env:computername) { + $localusertrim = $username -creplace '^[^\\]*\\', '' + $localusers.Add($localusertrim) | Out-Null + } + + } + } + + process { + if ($localusers -eq $field) { + Return $true + } else { + Return $false + } + } + end { + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 new file mode 100644 index 00000000..ec6631f2 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 @@ -0,0 +1,34 @@ +function Test-RegistryValueMatch { + + param ( + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()]$Path, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()]$Value, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()]$stringmatch + + ) + + $ErrorActionPreference = "SilentlyContinue" + $regvalue = Get-ItemPropertyValue -Path $Path -Name $Value + $ErrorActionPreference = "Continue" + $out = 'Value For ' + $Value + ' Is ' + $1 + ' On ' + $Path + + + if ([string]::IsNullOrEmpty($regvalue)) { + write-host 'KEY DOESNT EXIST OR IS EMPTY' + return $false + } else { + if ($regvalue -match ($stringmatch)) { + Write-Host $out + return $true + } else { + Write-Host $out + return $false + } + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 new file mode 100644 index 00000000..27596bc5 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 @@ -0,0 +1,67 @@ +# Function to validate that the user main folders are default and not redirected +function Test-UserFolderRedirect { + param ( + [Parameter(Mandatory = $true)] + [System.String] + $UserSid + ) + begin { + if ("HKEY_USERS" -notin (Get-psdrive | select-object name).Name) { + Write-ToLog "Mounting HKEY_USERS" + New-PSDrive -Name:("HKEY_USERS") -PSProvider:("Registry") -Root:("HKEY_USERS") | Out-Null + } + $UserFolders = @( "Desktop", "Documents", "Downloads", "Favorites", "Music", "Pictures", "Videos" ) + # Support doc for personal folders: https://support.microsoft.com/en-us/topic/operation-to-change-a-personal-folder-location-fails-in-windows-ffb95139-6dbb-821d-27ec-62c9aaccd720 + $regFoldersPath = "HKEY_USERS:\$($UserSid)_admu\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" + Write-ToLog -Message:("Checking User Shell Folders for USERSID: $($UserSid)") + } + process { + + if (Test-Path -Path $regFoldersPath) { + $redirectedDirectory = $false + # Save all the boolean to a hash table + foreach ($userFolder in $UserFolders) { + switch ($userFolder) { + "Desktop" { + $folderRegKey = "Desktop" + } + "Documents" { + $folderRegKey = "Personal" + } + "Downloads" { + $folderRegKey = "{374DE290-123F-4565-9164-39C4925E467B}" + } + "Favorites" { + $folderRegKey = "Favorites" + } + "Music" { + $folderRegKey = "My Music" + } + "Pictures" { + $folderRegKey = "My Pictures" + } + "Videos" { + $folderRegKey = "My Video" + } + } + # Get the registry value for the user folder + $folderRegKeyValue = (Get-Item -path $regFoldersPath ).GetValue($folderRegKey , '', 'DoNotExpandEnvironmentNames') + $defaultRegFolder = "%USERPROFILE%\$userFolder" + # If the registry value does not match the default path, set redirectedDirectory to true and log the error + if ($folderRegKeyValue -ne $defaultRegFolder) { + Write-ToLog -Message:("$($userFolder) path value: $($folderRegKeyValue) does not match default path - $($defaultRegFolder)") -Level Error + $redirectedDirectory = $true + } else { + Write-ToLog -Message:("User Shell Folder: $($userFolder) is default") + } + } + } else { + # If the registry path does not exist, set redirectedDirectory to true and log the error + Write-ToLog -Message:("User Shell registry folders not found in registry") -Level Error + $redirectedDirectory = $true + } + } + end { + return $redirectedDirectory + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Test-UserRegistryLoadState.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-UserRegistryLoadState.ps1 new file mode 100644 index 00000000..6cc672cb --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-UserRegistryLoadState.ps1 @@ -0,0 +1,74 @@ +Function Test-UserRegistryLoadState { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [ValidateScript( { Test-Path $_ })] + [System.String]$ProfilePath, + # User Security Identifier + [Parameter(Mandatory = $true)] + [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] + [System.String]$UserSid, + [Parameter(Mandatory = $false)] + [bool]$ValidateDirectory + ) + begin { + $results = REG QUERY HKU *>&1 + # Tests to check that the reg items are not loaded + If ($results -match $UserSid) { + Write-ToLog "REG Keys are loaded, attempting to unload" + try { + Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive root + Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes + } catch { + Write-AdmuErrorMessage -Error:("load_unload_error") + Throw "Could Not Unload User Registry During Test-UserRegistryLoadState Unload Process" + } + } + } + process { + try { + Set-UserRegistryLoadState -op "Load" -ProfilePath $ProfilePath -UserSid $UserSid -hive root + Set-UserRegistryLoadState -op "Load" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes + if ($ValidateDirectory) { + # return boolean for redirected user directories + $isFolderRedirect = Test-UserFolderRedirect -UserSid $UserSid + } else { + Write-ToLog "Skipping User Shell Folder Validation..." + } + } catch { + Write-AdmuErrorMessage -Error:("load_unload_error") + Throw "Could Not Load User Registry During Test-UserRegistryLoadState Load Process" + } + try { + Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive root + Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes + } catch { + Write-AdmuErrorMessage -Error:("load_unload_error") + + Throw "Could Not Unload User Registry During Test-UserRegistryLoadState Unload Process" + } + } + end { + $results = REG QUERY HKU *>&1 + # Tests to check that the reg items are not loaded + If ($results -match $UserSid) { + Write-ToLog "REG Keys are loaded, attempting to unload" + try { + Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive root + Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes + } catch { + Write-AdmuErrorMessage -Error:("load_unload_error") + throw "Registry Keys are still loaded after Test-UserRegistryLoadState Testing Exiting..." + } + } + # If isFolderRedirect is false throw error + if ($isFolderRedirect -and $ValidateDirectory) { + Write-AdmuErrorMessage -Error:("user_folder_redirection_error") + throw "Main user folders are redirected, exiting..." + } elseif ($ValidateDirectory -eq $false) { + Write-ToLog "Skipping User Shell Folder Validation..." + } else { + Write-ToLog "Main user folders are default for Usersid: $($UserSid), continuing..." + } + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 new file mode 100644 index 00000000..a26ca620 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 @@ -0,0 +1,48 @@ +function Test-UsernameOrSID { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + $usernameorsid + ) + Begin { + $sidPattern = "^S-\d-\d+-(\d+-){1,14}\d+$" + $localcomputersidprefix = ((Get-LocalUser | Select-Object -First 1).SID).AccountDomainSID.ToString() + $convertedUser = Convert-UserName $usernameorsid + $registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + $list = @() + foreach ($profile in $registyProfiles) { + $list += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath + } + $users = @() + foreach ($listItem in $list) { + $isValidFormat = [regex]::IsMatch($($listItem.PSChildName), $sidPattern); + # Get Valid SIDS + if ($isValidFormat) { + $users += [PSCustomObject]@{ + Name = Convert-SecurityIdentifier $listItem.PSChildName + SID = $listItem.PSChildName + } + } + } + } + process { + #check if sid, if valid sid and return sid + if ([regex]::IsMatch($usernameorsid, $sidPattern)) { + if (($usernameorsid -in $users.SID) -And !($users.SID.Contains($localcomputersidprefix))) { + # return, it's a valid SID + Write-ToLog "valid sid returning sid" + return $usernameorsid + } + } elseif ([regex]::IsMatch($convertedUser, $sidPattern)) { + if (($convertedUser -in $users.SID) -And !($users.SID.Contains($localcomputersidprefix))) { + # return, it's a valid SID + Write-ToLog "valid user returning sid" + return $convertedUser + } + } else { + Write-ToLog 'SID or Username is invalid' + throw 'SID or Username is invalid' + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Uninstall-Program.ps1 b/jumpcloud-ADMU/Powershell/Private/Uninstall-Program.ps1 new file mode 100644 index 00000000..a62e18c2 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Uninstall-Program.ps1 @@ -0,0 +1,19 @@ +function Uninstall-Program($programName) { + $Ver = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | + Get-ItemProperty | + Where-Object { $_.DisplayName -match $programName } | + Select-Object -Property DisplayName, UninstallString + + ForEach ($ver in $Ver) { + If ($ver.UninstallString -and $ver.DisplayName -match 'Jumpcloud') { + $uninst = $ver.UninstallString + & cmd /C $uninst /Silent | Out-Null + } If ($ver.UninstallString -and $ver.DisplayName -match 'AWS Command Line Interface') { + $uninst = $ver.UninstallString + & cmd /c $uninst /S | Out-Null + } else { + $uninst = $ver.UninstallString + & cmd /c $uninst /q /norestart | Out-Null + } + } +} diff --git a/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 new file mode 100644 index 00000000..77c691a1 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 @@ -0,0 +1,46 @@ +function Write-AdmuErrorMessage { + param ( + [string]$ErrorName + ) + switch ($ErrorName) { + "load_unload_error" { + Write-ToLog -Message "Load/Unload Error: The user registry cannot be loaded or unloaded. Verify that the admin running ADMU has permission to the user's NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors" -Level Error + + $Script:ErrorMessage = "Load/Unload Error: User registry cannot be loaded or unloaded. Click the link below for troubleshooting information." + } + "copy_error" { + Write-ToLog -Message:("Registry Copy Error: The user registry files can not be coppied. Verify that the admin running ADMU has permission to the user's NTUser.dat/ UsrClass.dat files, no user processes/ services are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + + $Script:ErrorMessage = "Registry Copy Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." + } + "rename_registry_file_error" { + Write-ToLog -message:("Registry Rename Error: Could not rename user registry file. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + + $Script:ErrorMessage = "Registry Rename Error: Registry files cannot be renamed. Click the link below for troubleshooting information." + } + "backup_error" { + Write-ToLog -Message:("Registry Backup Error: Could not take a backup of the user registry files. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + + $Script:ErrorMessage = "Registry Backup Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." + } + "user_init_error" { + Write-ToLog -Message:("User Initialization Error: The new local user was created but could not be initialized. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + + $Script:ErrorMessage = "User Initialization Error. Click the link below for troubleshooting information." + } + "user_create_error" { + Write-ToLog -Message:("User Creation Error: The new local user could not be created. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + + $Script:ErrorMessage = "User Creation Error. Click the link below for troubleshooting information." + } + "user_folder_redirection_error" { + Write-ToLog -Message:("User Folder Redirection Error: One of the user's main folder (Desktop, Downloads, Documents, Favorites, Pictures, Videos, Music) path is redirected. Verify that the user's main folders path are set to default and not redirected to another path (ie. Network Drive). Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + $Script:ErrorMessage = "User Folder Redirection Error. Click the link below for troubleshooting information." + } + Default { + Write-ToLog -Message:("Error occured, please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + + $Script:ErrorMessage = "Error occured. Click the link below for troubleshooting information." + } + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 new file mode 100644 index 00000000..bfc0aa58 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 @@ -0,0 +1,106 @@ +#Logging function +<# + .Synopsis + Write-ToLog writes a message to a specified log file with the current time stamp. + .DESCRIPTION + The Write-ToLog function is designed to add logging capability to other scripts. + In addition to writing output and/or verbose you can write to a log file for + later debugging. + .NOTES + Created by: Jason Wasser @wasserja + Modified: 11/24/2015 09:30:19 AM + .PARAMETER Message + Message is the content that you wish to add to the log file. + .PARAMETER Path + The path to the log file to which you would like to write. By default the function will + create the path and file if it does not exist. + .PARAMETER Level + Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational) + .EXAMPLE + Write-ToLog -Message 'Log message' + Writes the message to c:\Logs\PowerShellLog.log. + .EXAMPLE + Write-ToLog -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log + Writes the content to the specified log file and creates the path and file specified. + .EXAMPLE + Write-ToLog -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error + Writes the message to the specified log file as an error message, and writes the message to the error pipeline. + .LINK + https://gallery.technet.microsoft.com/scriptcenter/Write-ToLog-PowerShell-999c32d0 + #> +# Set a global parameter for debug logging + +Function Write-ToLog { + [CmdletBinding()] + Param + ( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][ValidateNotNullOrEmpty()][Alias("LogContent")][string]$Message + , [Parameter(Mandatory = $false)][Alias('LogPath')][string]$Path = "$(Get-WindowsDrive)\Windows\Temp\jcAdmu.log" + , [Parameter(Mandatory = $false)][ValidateSet("Error", "Warn", "Info", "Verbose")][string]$Level = "Info" + # Log all messages if $VerbosePreference is set to + ) + Begin { + # Set VerbosePreference to Continue so that verbose messages are displayed. + $VerbosePreference = 'Continue' + } + Process { + # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. + If (!(Test-Path $Path)) { + Write-Verbose "Creating $Path." + New-Item $Path -Force -ItemType File + } Else { + # Nothing to see here yet. + } + # Format Date for our Log File + $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + # Write message to error, warning, or verbose pipeline and specify $LevelText + if ($Script:AdminDebug) { + Switch ($Level) { + 'Error' { + Write-Error $Message + $LevelText = 'ERROR:' + } + 'Warn' { + Write-Warning $Message + $LevelText = 'WARNING:' + } + 'Info' { + Write-Verbose $Message + $LevelText = 'INFO:' + } + 'Verbose' { + Write-Verbose $Message + $LevelText = 'INFO:' + } + } + } else { + Switch ($Level) { + 'Error' { + Write-Error $Message + $LevelText = 'ERROR:' + } + 'Warn' { + $LevelText = 'WARNING:' + } + 'Info' { + $LevelText = 'INFO:' + } + 'Verbose' { + Write-Verbose $Message + $LevelText = 'INFO:' + } + } + } + + # Add the message to the log messages and space down + $logMessage = "$FormattedDate $LevelText $Message" + "`r`n" + if ($Script:ProgressBar) { + Update-LogTextBlock -LogText $logMessage -ProgressBar $Script:ProgressBar + } + # Write log entry to $Path + "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append + } + End { + + } +} \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Write-ToProgress.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-ToProgress.ps1 new file mode 100644 index 00000000..5f584371 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Write-ToProgress.ps1 @@ -0,0 +1,67 @@ +# Function to write progress to the progress bar or console +function Write-ToProgress { + param ( + [Parameter(Mandatory = $false)] + $form, + [Parameter(Mandatory = $false)] + $progressBar, + [Parameter(Mandatory = $true)] + $status, + [Parameter(Mandatory = $false)] + $logLevel, + [Parameter(Mandatory = $false)] + $username, + [Parameter(Mandatory = $false)] + $newLocalUsername, + [Parameter(Mandatory = $false)] + $profileSize, + [Parameter(Mandatory = $false)] + $LocalPath + + ) + # Create a hashtable of all status messages + $statusMessages = [ordered]@{ + "Init" = "Initializing Migration" + "Install" = "Installing JumpCloud Agent" + "BackupUserFiles" = "Backing up user profile" + "UserProfileUnit" = "Initializing new user profile" + "BackupRegHive" = "Backing up registry hive" + "VerifyRegHive" = "Verifying registry hive" + "CopyLocalReg" = "Copying local user registry" + "GetACL" = "Getting ACLs" + "CopyUser" = "Copying selected user to new user" + "CopyUserRegFiles" = "Copying user registry files" + "CopyMergedProfile" = "Copying merged profiles to destination profile path" + "CopyDefaultProtocols" = "Copying default protocol associations" + "ValidateUserPermissions" = "Validating user permissions" + "CreateRegEntries" = "Creating registry entries" + "DownloadUWPApps" = "Downloading UWP Apps" + "CheckADStatus" = "Checking AD Status" + "ConversionComplete" = "Profile conversion complete" + "MigrationComplete" = "Migration completed successfully" + } + # If status is error message, write to log + if ($logLevel -eq "Error") { + $statusMessage = $Status + $PercentComplete = 100 + } else { + # Get the status message + $statusMessage = $statusMessages[$status] + # Count the number of status messages + $statusCount = $statusMessages.Count + # Get the index of the status message using for loop + $statusIndex = [array]::IndexOf($statusMessages.Keys, $status) + # Calculate the percentage complete based on the index of the status message + $PercentComplete = ($statusIndex / ($statusCount - 1)) * 100 + } + if ($form) { + if ($username -or $newLocalUsername -or $profileSize -or $LocalPath) { + # Pass in the migration details to the progress bar + Update-ProgressForm -progressBar $progressBar -percentComplete $PercentComplete -Status $statusMessage -username $username -newLocalUsername $newLocalUsername -profileSize $profileSize -localPath $LocalPath + } else { + Update-ProgressForm -progressBar $progressBar -percentComplete $PercentComplete -Status $statusMessage -logLevel $logLevel + } + } else { + Write-Progress -Activity "Migration Progress" -percentComplete $percentComplete -status $statusMessage + } +} diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index 9ebfbf4d..b1cfcc4c 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -162,25 +162,6 @@ Describe 'Functions' { } } - Context 'DenyInteractiveLogonRight Function' -Skip { - #SeDenyInteractiveLogonRight not present in circleci instance - It 'User exists on system' { - # $objUser = New-Object System.Security.Principal.NTAccount("circleci") - # $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) - # DenyInteractiveLogonRight -SID $strSID.Value - # $secpolFile = "C:\Windows\temp\ur_orig.inf" - # if (Test-Path $secpolFile) - # { - # Remove-Item $secpolFile -Force - # } - # secedit /export /areas USER_RIGHTS /cfg C:\Windows\temp\ur_orig.inf - # $secpol = (Get-Content $secpolFile) - # $regvaluestring = $secpol | Where-Object { $_ -like "*SeDenyInteractiveLogonRight*" } - # $regvaluestring.Contains($strSID.Value) | Should -Be $true - } - - } - Context 'Register-NativeMethod Function' -Skip { # Register a C# Method to PWSH context we effectively test this with Migration tests } @@ -454,19 +435,6 @@ Describe 'Functions' { } } - Context 'Test-Domainusername Function' { - # Requires domainjoined system - It 'Test-Domainusername - exists' -skip { - - Test-Domainusername -field 'bob.lazar' | Should -Be $true - } - - It 'Test-Domainusername - does not exist' { - - Test-Domainusername -field 'bob.lazarz' | Should -Be $false - } - } - Context 'Install-JumpCloudAgent Function' { BeforeAll { Mock Get-WindowsDrive { Return "C:" } @@ -497,15 +465,15 @@ Describe 'Functions' { } } - Context 'Convert-SID Function' { + Context 'Convert-SecurityIdentifier Function' { BeforeAll { $newUserPassword = ConvertTo-SecureString -String 'Temp123!' -AsPlainText -Force New-localUser -Name 'sidTest' -password $newUserPassword -Description "Created By JumpCloud ADMU tests" New-LocalUserProfile -username:('sidTest') } - It 'Convert-SID - circleci SID' { + It 'Convert-SecurityIdentifier - circleci SID' { $circlecisid = (Get-WmiObject win32_userprofile | select-object Localpath, SID | where-object Localpath -eq 'C:\Users\sidTest' | Select-Object SID).SID - (Convert-SID -Sid $circlecisid) | Should -match 'sidTest' + (Convert-SecurityIdentifier -Sid $circlecisid) | Should -match 'sidTest' } } @@ -735,14 +703,14 @@ Describe 'Functions' { # Change the value of the folder Desktop to a different value $folderPath = "HKEY_USERS:\$($userSid)_admu\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" Set-ItemProperty -Path $folderpath -Name Desktop -Value "\\server\share\desktop" - {Test-UserFolderRedirect -UserSid $userSid} | Should -Throw + { Test-UserFolderRedirect -UserSid $userSid } | Should -Throw # Change the value of the folder Desktop back to the default value Set-ItemProperty -Path $folderpath -Name Desktop -Value "%USERPROFILE%\Desktop" } # Test for Invalid SID or Invalid User Shell Folder It 'Test-UserFolderRedirect - Invalid SID or Invalid User Shell Folder' { - {Test-UserFolderRedirect -UserSid "Invalid-3361044348-30300820-1001"} | Should -Throw + { Test-UserFolderRedirect -UserSid "Invalid-3361044348-30300820-1001" } | Should -Throw } } } From 31b398a6130b1e92632d3289b72663ac2e14a52e Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 7 Jan 2025 15:22:25 -0700 Subject: [PATCH 02/73] Move Start Migration --- jumpcloud-ADMU/Powershell/{ => Public}/Start-Migration.ps1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jumpcloud-ADMU/Powershell/{ => Public}/Start-Migration.ps1 (100%) diff --git a/jumpcloud-ADMU/Powershell/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 similarity index 100% rename from jumpcloud-ADMU/Powershell/Start-Migration.ps1 rename to jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 From 817cc8afee08c3a2650ecb448580fe500e5c1f9b Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 7 Jan 2025 15:26:21 -0700 Subject: [PATCH 03/73] remove functions from start-migration --- .../Powershell/Public/Start-Migration.ps1 | 1935 ----------------- 1 file changed, 1935 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index 79451076..cbbb6c5f 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -1,1938 +1,3 @@ -#region Functions -function Test-RegistryValueMatch { - - param ( - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()]$Path, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()]$Value, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()]$stringmatch - - ) - - $ErrorActionPreference = "SilentlyContinue" - $regvalue = Get-ItemPropertyValue -Path $Path -Name $Value - $ErrorActionPreference = "Continue" - $out = 'Value For ' + $Value + ' Is ' + $1 + ' On ' + $Path - - - if ([string]::IsNullOrEmpty($regvalue)) { - write-host 'KEY DOESNT EXIST OR IS EMPTY' - return $false - } else { - if ($regvalue -match ($stringmatch)) { - Write-Host $out - return $true - } else { - Write-Host $out - return $false - } - } -} -function Set-JCUserToSystemAssociation { - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$JcApiKey, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][ValidateLength(24, 24)][string]$JcOrgId, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$JcUserID, - [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][bool]$BindAsAdmin, - [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$UserAgent - ) - Begin { - $config = get-content "$WindowsDrive\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf" - $regex = 'systemKey\":\"(\w+)\"' - $systemKey = [regex]::Match($config, $regex).Groups[1].Value - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - If (!$systemKey) { - Write-ToLog -Message:("Could not find systemKey, aborting bind step") -Level:('Warn') - } - } - Process { - Write-ToLog -Message:("User matched in JumpCloud") - $Headers = @{ - 'Accept' = 'application/json'; - 'Content-Type' = 'application/json'; - 'x-api-key' = $JcApiKey; - 'x-org-id' = $JcOrgId; - } - $Form = @{ - 'op' = 'add'; - 'type' = 'system'; - 'id' = "$systemKey" - } - if ($BindAsAdmin) { - Write-ToLog -Message:("Bind As Admin specified. Setting sudo attributes for userID: $JcUserID") - $Form.Add("attributes", @{ - "sudo" = @{ - "enabled" = $true - "withoutPassword" = $false - } - } - ) - } else { - Write-ToLog -Message:("Bind As Admin NOT specified. userID: $JcUserID will be bound as a standard user") - } - $jsonForm = $Form | ConvertTo-Json - Try { - Write-ToLog -Message:("Attempting to bind userID: $JcUserID to systemID: $systemKey") - $Response = Invoke-WebRequest -Method 'Post' -Uri "https://console.jumpcloud.com/api/v2/users/$JcUserID/associations" -Headers $Headers -Body $jsonForm -UseBasicParsing -UserAgent $UserAgent - $StatusCode = $Response.StatusCode - } catch { - $errorMsg = $_.Exception.Message - $StatusCode = $_.Exception.Response.StatusCode.value__ - Write-ToLog -Message:("Could not bind user to system") -Level:('Warn') - } - - } - End { - # Associations post should return 204 success no content - if ($StatusCode -eq 204) { - Write-ToLog -Message:("Associations Endpoint returened statusCode $statusCode [success]") -Level:('Warn') - return $true - } else { - Write-ToLog -Message:("Associations Endpoint returened statusCode $statusCode | $errorMsg") -Level:('Warn') - return $false - } - } -} -function DenyInteractiveLogonRight { - param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - $SID - ) - process { - # Add migrating user to deny logon rights - $secpolFile = "C:\Windows\temp\ur_orig.inf" - if (Test-Path $secpolFile) { - Remove-Item $secpolFile -Force - } - secedit /export /areas USER_RIGHTS /cfg C:\Windows\temp\ur_orig.inf - $secpol = (Get-Content $secpolFile) - $regvaluestring = $secpol | Where-Object { $_ -like "*SeDenyInteractiveLogonRight*" } - $regvaluestringID = [array]::IndexOf($secpol, $regvaluestring) - $oldvalue = (($secpol | Select-String -Pattern 'SeDenyInteractiveLogonRight' | Out-String).trim()).substring(30) - $newvalue = ('*' + $SID + ',' + $oldvalue.trim()) - $secpol[$regvaluestringID] = 'SeDenyInteractiveLogonRight = ' + $newvalue - $secpol | out-file $windowsDrive\Windows\temp\ur_new.inf -force - secedit /configure /db secedit.sdb /cfg $windowsDrive\Windows\temp\ur_new.inf /areas USER_RIGHTS - } -} -function Register-NativeMethod { - [CmdletBinding()] - [Alias()] - [OutputType([int])] - Param - ( - # Param1 help description - [Parameter(Mandatory = $true, - ValueFromPipelineByPropertyName = $true, - Position = 0)] - [string]$dll, - - # Param2 help description - [Parameter(Mandatory = $true, - ValueFromPipelineByPropertyName = $true, - Position = 1)] - [string] - $methodSignature - ) - process { - $script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; } - } -} -function Add-NativeMethod { - [CmdletBinding()] - [Alias()] - [OutputType([int])] - Param($typeName = 'NativeMethods') - - process { - $nativeMethodsCode = $script:nativeMethods | ForEach-Object { " - [DllImport(`"$($_.Dll)`")] - public static extern $($_.Signature); - " } - - Add-Type @" - using System; - using System.Text; - using System.Runtime.InteropServices; - public static class $typeName { - $nativeMethodsCode - } -"@ - } -} -function New-LocalUserProfile { - - [CmdletBinding()] - [Alias()] - [OutputType([int])] - Param - ( - # Param1 help description - [Parameter(Mandatory = $true, - ValueFromPipelineByPropertyName = $true, - Position = 0)] - [string]$UserName - ) - process { - $methodname = 'UserEnvCP2' - $script:nativeMethods = @(); - - if (-not ([System.Management.Automation.PSTypeName]$methodname).Type) { - Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,` - [MarshalAs(UnmanagedType.LPWStr)] string pszUserName,` - [Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)"; - - Add-NativeMethod -typeName $methodname; - } - - $sb = new-object System.Text.StringBuilder(260); - $pathLen = $sb.Capacity; - - Write-ToLog "Creating user profile for $UserName" -Level Verbose - if ($UserName -eq $env:computername) { - Write-ToLog "$UserName Matches ComputerName" -Level Verbose - $objUser = New-Object System.Security.Principal.NTAccount("$env:computername\$UserName") - } else { - $objUser = New-Object System.Security.Principal.NTAccount($UserName) - } - $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) - $SID = $strSID.Value - - try { - $result = [UserEnvCP2]::CreateProfile($SID, $Username, $sb, $pathLen) - if ($result -eq '-2147024713') { - $status = "$userName is an existing account" - Write-ToLog "$username creation result: $result" - } elseif ($result -eq '-2147024809') { - $status = "$username Not Found" - Write-ToLog "$username Creation Result: $result" - } elseif ($result -eq 0) { - $status = "$username Profile has been created" - Write-ToLog "$username Creation Result: $result" - } else { - $status = "$UserName unknown return result: $result" - } - } catch { - Write-Error $_.Exception.Message; - # break; - } - # $status - } - end { - return $SID - } -} -function Remove-LocalUserProfile { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [System.String] - $UserName - ) - Begin { - # Validate that the user was just created by the ADMU - $removeUser = $false - $users = Get-LocalUser - foreach ($user in $users) { - # we only want to remove users with description "Created By JumpCloud ADMU" - if ( $user.name -match $UserName -And $user.description -eq "Created By JumpCloud ADMU" ) { - $UserSid = Get-SID -User $UserName - $UserPath = Get-ProfileImagePath -UserSid $UserSid - # Set RemoveUser bool to true - $removeUser = $true - } - } - if (!$removeUser) { - throw "Username match not found, not reversing" - } - } - Process { - # Remove the profile - if ($removeUser) { - # Remove the User - Remove-LocalUser -Name $UserName - # Remove the User Profile - if (Test-Path -Path $UserPath) { - $Group = New-Object System.Security.Principal.NTAccount("Builtin", "Administrators") - $ACL = Get-ACL $UserPath - $ACL.SetOwner($Group) - - Get-ChildItem $UserPath -Recurse -Force -errorAction SilentlyContinue | ForEach-Object { - Try { - Set-ACL -AclObject $ACL -Path $_.fullname -errorAction SilentlyContinue - } catch [System.Management.Automation.ItemNotFoundException] { - Write-Verbose 'ItemNotFound : $_' - } - } - # icacls $($UserPath) /grant administrators:F /T - # takeown /f $($UserPath) /r /d y - Remove-Item -Path $($UserPath) -Force -Recurse #-ErrorAction SilentlyContinue - } - # Remove the User SID - # TODO: if the profile SID is loaded in registry skip this and note in log - # Match the user SID - $matchedKey = get-childitem -path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' | Where-Object { $_.Name -match $UserSid } - # Set the Matched Key Path to PSPath so PowerShell can use the path - $matchedKeyPath = $($matchedKey.Name) -replace "HKEY_LOCAL_MACHINE", "HKLM:" - # Remove the UserSid Key from the ProfileList - Remove-Item -Path "$matchedKeyPath" -Recurse - } - } - End { - # Output some info - Write-ToLog -message:("$UserName's account, profile and Registry Key SID were removed") - } -} - -# Reg Functions adapted from: -# https://social.technet.microsoft.com/Forums/windows/en-US/9f517a39-8dc8-49d3-82b3-96671e2b6f45/powershell-set-registry-key-owner-to-the-system-user-throws-error?forum=winserverpowershell - -function Set-ValueToKey([Microsoft.Win32.RegistryHive]$registryRoot, [string]$keyPath, [string]$name, [System.Object]$value, [Microsoft.Win32.RegistryValueKind]$regValueKind) { - $regRights = [System.Security.AccessControl.RegistryRights]::SetValue - $permCheck = [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree - $Key = [Microsoft.Win32.Registry]::$registryRoot.OpenSubKey($keyPath, $permCheck, $regRights) - Write-ToLog -Message:("Setting value with properties [name:$name, value:$value, value type:$regValueKind]") - $Key.SetValue($name, $value, $regValueKind) - $key.Close() -} - -function New-RegKey([string]$keyPath, [Microsoft.Win32.RegistryHive]$registryRoot) { - $Key = [Microsoft.Win32.Registry]::$registryRoot.CreateSubKey($keyPath) - Write-ToLog -Message:("Setting key at [KeyPath:$keyPath]") - $key.Close() -} - -#username To SID Function -function Get-SID ([string]$User) { - $objUser = New-Object System.Security.Principal.NTAccount($User) - $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) - $strSID.Value -} - -# Get processes to display if we could not load/ unload registry hive: -function Get-ProcessByOwner { - [CmdletBinding()] - param ( - # the username to of which to search active processes - [Parameter(Mandatory = $true, ParameterSetName = "ByUsername")] - [System.String] - $username, - # the account security identifier of which to search processes - [Parameter(Mandatory = $true, ParameterSetName = "BySID")] - [System.String] - $SID - ) - - begin { - switch ($PSBoundParameters.Keys) { - 'username' { - # validate username - $accountSid = Convert-UserName -user $username - $domainUsername = Convert-Sid -Sid $accountSid - } - 'SID' { - # validate SID - $domainUsername = Convert-Sid -Sid $SID - } - } - } - process { - $processList = New-Object System.Collections.ArrayList - $processes = Get-Process - foreach ($process in $processes) { - if ($process.id) { - # TODO: processItem would throw a null value exception - $processItem = (Get-WmiObject -Class Win32_Process -Filter:("ProcessId = $($Process.Id)")) - if (![string]::IsNullOrEmpty($processItem)) { - # Create null value check for processItem - $owner = $processItem.GetOwner() - $processList.Add( - [PSCustomObject]@{ - ProcessName = if ($process.Name) { - $process.Name - } else { - "NA" - } - ProcessId = if ($process.Id) { - $process.Id - } else { - "NA" - } - Owner = "$($owner.Domain)\$($owner.User)" - } - ) | Out-Null - } - } - } - # Filter Process List by User: - $processList = $processList | Where-Object { $_.Owner -eq $domainUsername } - } - - end { - Write-ToLog -Message:("Getting Processes for: $domainUsername") - Write-ToLog -Message:("Processes found: $($processList.count)") - return $processList - } -} - -function Show-ProcessListResult { - [CmdletBinding()] - param ( - # processList from Get-ProcessByOwner - [Parameter(Mandatory = $true)] - [System.Object] - $ProcessList, - # domainUsername from Get-ProcessByOwner - [Parameter(Mandatory = $true)] - [System.String] - $domainUsername - ) - - begin { - if (-not $ProcessList) { - Write-ToLog -Message:("No system processes were found for $domainUsername") - return - } else { - Write-ToLog -Message:("$($ProcessList.count) processes were found for $domainUsername") - } - } - - process { - Write-ToLog "The following processes were found running under $domainUsername's account" - foreach ($process in $ProcessList) { - Write-ToLog -Message:("ProcessName: $($Process.ProcessName) | ProcessId: $($Process.ProcessId)") - } - } - # TODO: Get Processes not owned by user: i.e. search open handles in memory that have been accessed by file -} -function Close-ProcessByOwner { - [CmdletBinding()] - param ( - # Parameter help description - [Parameter(Mandatory = $true)] - [System.Object] - $ProcesssList, - # force close processes - [Parameter()] - [bool] - $force - ) - - begin { - $resultList = New-Object System.Collections.ArrayList - } - - process { - switch ($force) { - $true { - foreach ($item in $ProcesssList) { - Write-ToLog "Attempting to close processID: $($item.ProcessId)" - $tkStatus = taskkill /t /f /PID $item.ProcessId 2>&1 - $tkSuccess = if ($tkStatus -match "ERROR") { - $false - } else { - $true - } - $resultList.Add( - [PSCustomObject]@{ - ProcessName = $item.ProcessName - ProcessID = $item.ProcessId - Closed = $tkSuccess - } - ) | Out-Null - } - } - $false { - foreach ($item in $ProcesssList) { - $resultList.Add( - [PSCustomObject]@{ - ProcessName = $item.ProcessName - ProcessID = $item.ProcessId - Closed = "NA" - } - ) | Out-Null - } - } - } - - # TODO: wait 1 -5 sec to ensure NTUser is closed - Start-Sleep 1 - } - end { - return $resultList - } -} -function Set-UserRegistryLoadState { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateSet("Unload", "Load")] - [System.String]$op, - [Parameter(Mandatory = $true)] - [ValidateSet("classes", "root")] - [System.String]$hive, - [Parameter(Mandatory = $true)] - [ValidateScript( { Test-Path $_ })] - [System.String]$ProfilePath, - # User Security Identifier - [Parameter(Mandatory = $true)] - [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] - [System.String]$UserSid, - [Parameter()] - [System.Int32]$counter = 0 - ) - begin { - Write-ToLog -Message:("## Begin Registry $op $UserSid ##") - switch ($hive) { - "classes" { - $key = "HKU\$($UserSid)_Classes_admu" - $hiveFile = "$ProfilePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" - } - "root" { - $key = "HKU\$($UserSid)_admu" - $hiveFile = "$ProfilePath\NTUSER.DAT.BAK" - } - } - If ($counter -ge 0) { - $counter += 1 - } - if ($counter -gt 3) { - # if we've tried to close the hive three times, throw error - throw "Registry $op $key failed" - } - } - process { - $username = Convert-Sid $UserSid - switch ($op) { - "Load" { - switch ($hive) { - "root" { - [gc]::collect() - $results = Set-RegistryExe -op Load -hive root -UserSid $UserSid -ProfilePath $ProfilePath - if ($results) { - Write-ToLog "Load Successful $results" - } else { - $processList = Get-ProcessByOwner -username $username - if ($processList) { - Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess - } - Set-UserRegistryLoadstate -op Load -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive root - } - } - "classes" { - [gc]::collect() - $results = Set-RegistryExe -op Load -hive classes -UserSid $UserSid -ProfilePath $ProfilePath - if ($results) { - Write-ToLog "Load Successful $results" - } else { - $processList = Get-ProcessByOwner -username $username - if ($processList) { - Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess - } - Set-UserRegistryLoadstate -op Load -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive classes - } - } - } - - - } - "Unload" { - switch ($hive) { - "root" { - [gc]::collect() - - $results = Set-RegistryExe -op Unload -hive root -UserSid $UserSid -ProfilePath $ProfilePath - if ($results) { - Write-ToLog "Unload Successful $results" - - } else { - $processList = Get-ProcessByOwner -username $username - if ($processList) { - Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess - } - Set-UserRegistryLoadstate -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive root - } - } - "classes" { - [gc]::collect() - - $results = Set-RegistryExe -op Unload -hive classes -UserSid $UserSid -ProfilePath $ProfilePath - if ($results) { - Write-ToLog "Unload Successful $results" - - } else { - $processList = Get-ProcessByOwner -username $username - if ($processList) { - Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess - } - Set-UserRegistryLoadstate -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive classes - } - } - } - } - } - } - end { - Write-ToLog -Message:("## End Registry $op $UserSid ##") - - } -} - -function Get-RegistryExeStatus { - [CmdletBinding()] - [OutputType([bool])] - param ( - [Parameter()] - [System.Object] - $resultsObject - ) - # if resultsObject has an exception, the command failed: - if ($resultsObject.Exception) { - # write the warning - Write-Warning "$($resultsObject.TargetObject)" - Write-Warning "$($resultsObject.InvocationInfo.PositionMessage)" - - # return false - $status = $false - } else { - # return true - $status = $true - } - # return true or false - return $status -} -function Set-RegistryExe { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateSet("Unload", "Load")] - [System.String]$op, - [ValidateSet("classes", "root")] - [System.String]$hive, - [Parameter(Mandatory = $true)] - [ValidateScript( { Test-Path $_ })] - [System.String]$ProfilePath, - # User Security Identifier - [Parameter(Mandatory = $true)] - [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] - [System.String]$UserSid - ) - begin { - switch ($hive) { - "classes" { - $key = "HKU\$($UserSid)_Classes_admu" - $hiveFile = "$ProfilePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" - } - "root" { - $key = "HKU\$($UserSid)_admu" - $hiveFile = "$ProfilePath\NTUSER.DAT.BAK" - } - } - } - process { - switch ($op) { - "Load" { - Write-ToLog "REG LOAD $KEY $hiveFile" - $results = REG LOAD $key $hiveFile *>&1 - } - "Unload" { - Write-ToLog "REG UNLOAD $KEY" - $results = REG UNLOAD $key *>&1 - } - } - $status = Get-RegistryExeStatus $results - } - end { - # Status here will be either true or false depending on whether or not the tool was able to perform the registry action requested - return $status - } - -} - -function Test-FileAttribute { - [CmdletBinding()] - param ( - # Profile path - [Parameter(Mandatory = $true)] - [ValidateScript( { Test-Path $_ })] - [System.String]$ProfilePath, - # Attribute to Test - [Parameter(Mandatory = $true)] - [ValidateSet("ReadOnly", "Hidden", "System", "Archive", "Normal", "Temporary", "Offline")] - [System.String] - $Attribute - ) - - begin { - $profileProperties = Get-ItemProperty -Path $ProfilePath - } - - process { - $attributes = $($profileProperties.Attributes) - } - - end { - if ($attributes -match $Attribute) { - return $true - } else { - return $false - } - } -} -function Set-FileAttribute { - [CmdletBinding()] - param ( - # Profile path - [Parameter(Mandatory = $true)] - [ValidateScript( { Test-Path $_ })] - [System.String] - $ProfilePath, - # Attribute to Remove - [Parameter(Mandatory = $true)] - [ValidateSet("ReadOnly", "Hidden", "System", "Archive", "Normal", "Temporary", "Offline")] - [System.String] - $Attribute, - # Operation verb (add/ remove) - [Parameter(Mandatory = $true)] - [ValidateSet( "Add", "Remove" )] - [System.String] - $Operation - ) - - begin { - $profilePropertiesBefore = Get-ItemProperty -Path $ProfilePath - $attributesBefore = $($profilePropertiesBefore.Attributes) - } - - process { - Write-ToLog "$profilePath attributes before: $($attributesBefore)" - # remove item with bitwise operators, keeping what was set but removing the $attribute - switch ($Operation) { - "Remove" { - $profilePropertiesBefore.Attributes = $profilePropertiesBefore.Attributes -band -bnot [System.IO.FileAttributes]::$Attribute - } - "Add" { - $profilePropertiesBefore.Attributes = $profilePropertiesBefore.Attributes -bxor [System.IO.FileAttributes]::$Attribute - } - } - $attributeTest = Test-FileAttribute -ProfilePath $ProfilePath -Attribute $Attribute - } - end { - $profilePropertiesAfter = Get-ItemProperty -Path $ProfilePath - $attributesAfter = $($profilePropertiesBefore.Attributes) - Write-ToLog "$profilePath attributes after: $($attributesAfter)" - - if ($attributeTest) { - return $true - } else { - return $false - } - } -} - - -Function Test-UserRegistryLoadState { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidateScript( { Test-Path $_ })] - [System.String]$ProfilePath, - # User Security Identifier - [Parameter(Mandatory = $true)] - [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] - [System.String]$UserSid, - [Parameter(Mandatory = $false)] - [bool]$ValidateDirectory - ) - begin { - $results = REG QUERY HKU *>&1 - # Tests to check that the reg items are not loaded - If ($results -match $UserSid) { - Write-ToLog "REG Keys are loaded, attempting to unload" - try { - Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive root - Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes - } catch { - Write-AdmuErrorMessage -Error:("load_unload_error") - Throw "Could Not Unload User Registry During Test-UserRegistryLoadState Unload Process" - } - } - } - process { - try { - Set-UserRegistryLoadState -op "Load" -ProfilePath $ProfilePath -UserSid $UserSid -hive root - Set-UserRegistryLoadState -op "Load" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes - if ($ValidateDirectory) { - # return boolean for redirected user directories - $isFolderRedirect = Test-UserFolderRedirect -UserSid $UserSid - } else { - Write-ToLog "Skipping User Shell Folder Validation..." - } - } catch { - Write-AdmuErrorMessage -Error:("load_unload_error") - Throw "Could Not Load User Registry During Test-UserRegistryLoadState Load Process" - } - try { - Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive root - Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes - } catch { - Write-AdmuErrorMessage -Error:("load_unload_error") - - Throw "Could Not Unload User Registry During Test-UserRegistryLoadState Unload Process" - } - } - end { - $results = REG QUERY HKU *>&1 - # Tests to check that the reg items are not loaded - If ($results -match $UserSid) { - Write-ToLog "REG Keys are loaded, attempting to unload" - try { - Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive root - Set-UserRegistryLoadState -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -hive classes - } catch { - Write-AdmuErrorMessage -Error:("load_unload_error") - throw "Registry Keys are still loaded after Test-UserRegistryLoadState Testing Exiting..." - } - } - # If isFolderRedirect is false throw error - if ($isFolderRedirect -and $ValidateDirectory) { - Write-AdmuErrorMessage -Error:("user_folder_redirection_error") - throw "Main user folders are redirected, exiting..." - } elseif ($ValidateDirectory -eq $false) { - Write-ToLog "Skipping User Shell Folder Validation..." - } else { - Write-ToLog "Main user folders are default for Usersid: $($UserSid), continuing..." - } - } -} - - -Function Backup-RegistryHive { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [System.String] - $profileImagePath, - # Parameter help description - [Parameter(Mandatory = $true)] - [System.String] - $SID - ) - begin { - # get sid from PIP: - $domainUsername = Convert-Sid -Sid $SID - } - process { - try { - Copy-Item -Path "$profileImagePath\NTUSER.DAT" -Destination "$profileImagePath\NTUSER.DAT.BAK" -ErrorAction Stop - Copy-Item -Path "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat" -Destination "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" -ErrorAction Stop - } catch { - $processList = Get-ProcessByOwner -username $domainUsername - if ($processList) { - Show-ProcessListResult -ProcessList $processList -domainUsername $domainUsername - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess - } - try { - Write-ToLog -Message("Initial backup was not successful, trying again...") - Write-ToLog $CloseResults - Start-Sleep 1 - # retry: - Copy-Item -Path "$profileImagePath\NTUSER.DAT" -Destination "$profileImagePath\NTUSER.DAT.BAK" -ErrorAction Stop - Copy-Item -Path "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat" -Destination "$profileImagePath\AppData\Local\Microsoft\Windows\UsrClass.dat.bak" -ErrorAction Stop - } catch { - Write-ToLog -Message("Could Not Backup Registry Hives in $($profileImagePath): Exiting...") - Write-AdmuErrorMessage -Error:("backup_error") - Write-ToLog -Message($_.Exception.Message) - throw "Could Not Backup Registry Hives in $($profileImagePath): Exiting..." - } - } - } -} - -Function Get-ProfileImagePath { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [ValidatePattern("^S-\d-\d+-(\d+-){1,14}\d+$")] - [System.String] - $UserSid - ) - $profileImagePath = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' + $UserSid) -Name 'ProfileImagePath' - if ([System.String]::IsNullOrEmpty($profileImagePath)) { - Write-ToLog -Message("Could not get the profile path for $UserSid exiting...") -level Warn - throw "Could not get the profile path for $UserSid exiting..." - } else { - return $profileImagePath - } -} -Function Get-WindowsDrive { - $drive = (Get-WmiObject Win32_OperatingSystem).SystemDrive - return $drive -} - -#Logging function -<# - .Synopsis - Write-ToLog writes a message to a specified log file with the current time stamp. - .DESCRIPTION - The Write-ToLog function is designed to add logging capability to other scripts. - In addition to writing output and/or verbose you can write to a log file for - later debugging. - .NOTES - Created by: Jason Wasser @wasserja - Modified: 11/24/2015 09:30:19 AM - .PARAMETER Message - Message is the content that you wish to add to the log file. - .PARAMETER Path - The path to the log file to which you would like to write. By default the function will - create the path and file if it does not exist. - .PARAMETER Level - Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational) - .EXAMPLE - Write-ToLog -Message 'Log message' - Writes the message to c:\Logs\PowerShellLog.log. - .EXAMPLE - Write-ToLog -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log - Writes the content to the specified log file and creates the path and file specified. - .EXAMPLE - Write-ToLog -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error - Writes the message to the specified log file as an error message, and writes the message to the error pipeline. - .LINK - https://gallery.technet.microsoft.com/scriptcenter/Write-ToLog-PowerShell-999c32d0 - #> -# Set a global parameter for debug logging - -Function Write-ToLog { - [CmdletBinding()] - Param - ( - [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][ValidateNotNullOrEmpty()][Alias("LogContent")][string]$Message - , [Parameter(Mandatory = $false)][Alias('LogPath')][string]$Path = "$(Get-WindowsDrive)\Windows\Temp\jcAdmu.log" - , [Parameter(Mandatory = $false)][ValidateSet("Error", "Warn", "Info", "Verbose")][string]$Level = "Info" - # Log all messages if $VerbosePreference is set to - ) - Begin { - # Set VerbosePreference to Continue so that verbose messages are displayed. - $VerbosePreference = 'Continue' - } - Process { - # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. - If (!(Test-Path $Path)) { - Write-Verbose "Creating $Path." - New-Item $Path -Force -ItemType File - } Else { - # Nothing to see here yet. - } - # Format Date for our Log File - $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - # Write message to error, warning, or verbose pipeline and specify $LevelText - if ($Script:AdminDebug) { - Switch ($Level) { - 'Error' { - Write-Error $Message - $LevelText = 'ERROR:' - } - 'Warn' { - Write-Warning $Message - $LevelText = 'WARNING:' - } - 'Info' { - Write-Verbose $Message - $LevelText = 'INFO:' - } - 'Verbose' { - Write-Verbose $Message - $LevelText = 'INFO:' - } - } - } else { - Switch ($Level) { - 'Error' { - Write-Error $Message - $LevelText = 'ERROR:' - } - 'Warn' { - $LevelText = 'WARNING:' - } - 'Info' { - $LevelText = 'INFO:' - } - 'Verbose' { - Write-Verbose $Message - $LevelText = 'INFO:' - } - } - } - - # Add the message to the log messages and space down - $logMessage = "$FormattedDate $LevelText $Message" + "`r`n" - if ($Script:ProgressBar) { - Update-LogTextBlock -LogText $logMessage -ProgressBar $Script:ProgressBar - } - # Write log entry to $Path - "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append - } - End { - - } -} -Function Remove-ItemIfExist { - [CmdletBinding(SupportsShouldProcess = $true)] - Param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][String[]]$Path - , [Switch]$Recurse - ) - Process { - Try { - If (Test-Path -Path:($Path)) { - Remove-Item -Path:($Path) -Recurse:($Recurse) - } - } Catch { - Write-ToLog -Message ('Removal Of Temp Files & Folders Failed') -Level Warn - } - } -} -# Check reg for program uninstall string and silently uninstall -function Uninstall-Program($programName) { - $Ver = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | - Get-ItemProperty | - Where-Object { $_.DisplayName -match $programName } | - Select-Object -Property DisplayName, UninstallString - - ForEach ($ver in $Ver) { - If ($ver.UninstallString -and $ver.DisplayName -match 'Jumpcloud') { - $uninst = $ver.UninstallString - & cmd /C $uninst /Silent | Out-Null - } If ($ver.UninstallString -and $ver.DisplayName -match 'AWS Command Line Interface') { - $uninst = $ver.UninstallString - & cmd /c $uninst /S | Out-Null - } else { - $uninst = $ver.UninstallString - & cmd /c $uninst /q /norestart | Out-Null - } - } -} - -#Start process and wait then close after 5mins -Function Start-NewProcess([string]$pfile, [string]$arguments, [int32]$Timeout = 300000) { - $p = New-Object System.Diagnostics.Process; - $p.StartInfo.FileName = $pfile; - $p.StartInfo.Arguments = $arguments - [void]$p.Start(); - If (! $p.WaitForExit($Timeout)) { - Write-ToLog -Message "Windows ADK Setup did not complete after 5mins"; - Get-Process | Where-Object { $_.Name -like "adksetup*" } | Stop-Process - } -} - -#Validation functions -Function Test-IsNotEmpty ([System.String] $field) { - If (([System.String]::IsNullOrEmpty($field))) { - Return $true - } Else { - Return $false - } -} -Function Test-CharLen { - [CmdletBinding()] - param ( - # Char Length to test - [Parameter(Mandatory = $true)] - [System.Int32] - $len, - # String to test #allow false to allow for searching empty strings - [Parameter(Mandatory = $false)] - [System.String] - $testString - ) - If ($testString.Length -eq $len) { - Return $true - } Else { - Return $false - } -} -Function Test-HasNoSpace ([System.String] $field) { - If ($field -like "* *") { - Return $false - } Else { - Return $true - } -} - -function Test-Localusername { - [CmdletBinding()] - param ( - [system.array] $field - ) - begin { - $win32UserProfiles = Get-WmiObject -Class:('Win32_UserProfile') -Property * | Where-Object { $_.Special -eq $false } - $users = $win32UserProfiles | Select-Object -ExpandProperty "SID" | Convert-Sid - $localusers = new-object system.collections.arraylist - foreach ($username in $users) { - $domain = ($username -split '\\')[0] - if ($domain -match $env:computername) { - $localusertrim = $username -creplace '^[^\\]*\\', '' - $localusers.Add($localusertrim) | Out-Null - } - - } - } - - process { - if ($localusers -eq $field) { - Return $true - } else { - Return $false - } - } - end { - } -} - -function Test-Domainusername { - [CmdletBinding()] - param ( - [system.array] $field - ) - begin { - $win32UserProfiles = Get-WmiObject -Class:('Win32_UserProfile') -Property * | Where-Object { $_.Special -eq $false } - $users = $win32UserProfiles | Select-Object -ExpandProperty "SID" | Convert-Sid - $domainusers = new-object system.collections.arraylist - foreach ($username in $users) { - if ($username -match (Get-NetBiosName) -or ($username -match 'AZUREAD')) { - $domainusertrim = $username -creplace '^[^\\]*\\', '' - $domainusers.Add($domainusertrim) | Out-Null - } - } - } - process { - if ($domainusers -eq $field) { - Return $true - } else { - Return $false - } - } - end { - } -} - -function Test-JumpCloudSystemKey { - [CmdletBinding()] - [OutputType([System.Boolean])] - param ( - [Parameter()] - [System.String] - $WindowsDrive - ) - - process { - $config = get-content "$WindowsDrive\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf" -ErrorVariable configExitCode -ErrorAction SilentlyContinue - if ($configExitCode) { - $message += "JumpCloud Agent is not installed on this system`nPlease also enter your Connect Key to install JumpCloud" - $wshell = New-Object -ComObject Wscript.Shell - $var = $wshell.Popup("$message", 0, "ADMU Status", 0x0 + 0x40) - return $false - } else { - return $true - } - } -} -function Test-JumpCloudUsername { - [CmdletBinding()] - [OutputType([System.Boolean])] - [OutputType([System.Object[]])] - param ( - [Parameter()] - [System.String] - $JumpCloudApiKey, - [Parameter()] - [System.String] - $JumpCloudOrgID, - [Parameter()] - [System.String] - $Username, - [Parameter()] - [System.Boolean] - $prompt = $false - ) - Begin { - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $Headers = @{ - 'Accept' = 'application/json'; - 'Content-Type' = 'application/json'; - 'x-api-key' = $JumpCloudApiKey; - 'x-org-id' = $JumpCloudOrgID; - } - - $Form = @{ - "filter" = @{ - 'and' = @( - @{'username' = @{'$regex' = "(?i)(`^$($Username)`$)" } } - ) - } - "fields" = "username , systemUsername" - } - $Body = $Form | ConvertTo-Json -Depth 4 - } - Process { - Try { - # Write-ToLog "Searching JC for: $Username" - $Response = Invoke-WebRequest -Method 'Post' -Uri "https://console.jumpcloud.com/api/search/systemusers" -Headers $Headers -Body $Body -UseBasicParsing - $Results = $Response.Content | ConvertFrom-Json - - $StatusCode = $Response.StatusCode - } catch { - $StatusCode = $_.Exception.Response.StatusCode.value__ - Write-ToLog -Message "Status Code $($StatusCode)" - } - } - End { - # Search User should return 200 success - If ($StatusCode -ne 200) { - Write-ToLog -Message "JumpCloud username could not be found" - Return $false, $null, $null - } - If ($Results.totalCount -eq 1 -and $($Results.results[0].username) -eq $Username) { - # write-host $Results.results[0]._id - Write-ToLog -Message "Identified JumpCloud User`nUsername: $($Results.results[0].username)`nID: $($Results.results[0]._id)" - if ($Results.results[0].SystemUsername) { - Write-ToLog -Message "JumpCloud User have a Local Account User set: $($Results.results[0].SystemUsername)" - return $true, $Results.results[0]._id, $Results.results[0].SystemUsername - } else { - return $true, $Results.results[0]._id, $null - } - - - } else { - if ($prompt) { - $message += "$Username is not a valid JumpCloud User`nPlease enter a valid JumpCloud Username" - $wshell = New-Object -ComObject Wscript.Shell - $var = $wshell.Popup("$message", 0, "ADMU Status", 0x0 + 0x40) - } - Return $false, $null, $null - } - } -} -function Get-mtpOrganization { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [System.String] - $apiKey, - [Parameter()] - [System.String] - $orgID, - [parameter()] - [switch] - $inputType - ) - begin { - $skip = 0 - $limit = 100 - $paginate = $true - $Headers = @{ - 'Content-Type' = 'application/json'; - 'Accept' = 'application/json'; - 'x-api-key' = "$($apiKey)"; - } - $results = @() - if ($orgID) { - Write-ToLog -Message "OrgID specified, attempting to validate org..." - $baseURl = "https://console.jumpcloud.com/api/organizations/$($orgID)" - $Request = Invoke-WebRequest -Uri "$($baseUrl)?limit=$($limit)&skip=$($skip)" -Method Get -Headers $Headers -UseBasicParsing - $Content = $Request.Content | ConvertFrom-Json - $results += $Content - } else { - Write-ToLog -Message "No OrgID specified, attempting to search for valid orgs..." - while ($paginate) { - $baseUrl = "https://console.jumpcloud.com/api/organizations" - $Request = Invoke-WebRequest -Uri "$($baseUrl)?limit=$($limit)&skip=$($skip)" -Method Get -Headers $Headers -UseBasicParsing - $Content = $Request.Content | ConvertFrom-Json - $results += $Content.results - if ($Content.results.Count -eq $limit) { - $skip += $limit - } else { - $paginate = $false - } - } - } - } - process { - # if there's only one org return found org, else prompt for selection - if (($results.count -eq 1) -And ($($results._id))) { - Write-ToLog -Message "API Key Validated`nOrgName: $($results.DisplayName)" - $orgs = $results._id, $results.DisplayName - } elseif (($results.count -gt 1)) { - Write-ToLog -Message "Found $($results.count) orgs with the specifed API Key" - # initial prompt for MTP selection - switch ($inputType) { - $true { - Write-ToLog -Message "Prompting for MTP Admin Selection" - $orgs = show-mtpSelection -Orgs $results - Write-ToLog -Message "API Key Validated`nOrgName: $($orgs[1])" - } - Default { - Write-ToLog -Message "API Key appears to be a MTP Admin Key. Please specify the JumpCloudOrgID Parameter and try again" - throw "API Key appears to be a MTP Admin Key. Please specify the JumpCloudOrgID Parameter and try again" - } - } - } else { - Write-ToLog -Message "No orgs matched provided API Key" - $orgs = $false - } - - } - end { - #returned org as an object [0]=id [1]=dispalyName - return $orgs - } -} - -Function Install-JumpCloudAgent( - [System.String]$AGENT_INSTALLER_URL - , [System.String]$AGENT_INSTALLER_PATH - , [System.String]$AGENT_PATH - , [System.String]$AGENT_BINARY_NAME - , [System.String]$AGENT_CONF_PATH - , [System.String]$JumpCloudConnectKey -) { - $AgentService = Get-Service -Name "jumpcloud-agent" -ErrorAction SilentlyContinue - If (!$AgentService) { - Write-ToLog -Message:('Downloading JCAgent Installer') -Level Verbose - #Download Installer - if ((Test-Path $AGENT_INSTALLER_PATH)) { - Write-ToLog -Message:('JumpCloud Agent Already Downloaded') -Level Verbose - } else { - (New-Object System.Net.WebClient).DownloadFile("${AGENT_INSTALLER_URL}", ($AGENT_INSTALLER_PATH)) - Write-ToLog -Message:('JumpCloud Agent Download Complete') -Level Verbose - } - Write-ToLog -Message:('Running JCAgent Installer') -Level Verbose - Write-ToLog -Message:("LogPath: $env:TEMP\jcUpdate.log") - # run .MSI installer - msiexec /i $AGENT_INSTALLER_PATH /quiet /L "$env:TEMP\jcUpdate.log" JCINSTALLERARGUMENTS=`"-k $($JumpCloudConnectKey) /VERYSILENT /NORESTART /NOCLOSEAPPLICATIONS`" - # perform installation checks: - for ($i = 0; $i -le 17; $i++) { - Write-ToLog -Message:('Waiting on JCAgent Installer...') - Start-Sleep -Seconds 30 - #Output the errors encountered - $AgentService = Get-Service -Name "jumpcloud-agent" -ErrorAction SilentlyContinue - if ($AgentService.Status -eq 'Running') { - Write-ToLog 'JumpCloud Agent Succesfully Installed' - $agentInstalled = $true - break - } - if (($i -eq 17) -and ($AgentService.Status -ne 'Running')) { - Write-ToLog -Message:('JCAgent did not install in the expected window') -Level Error - $agentInstalled = $false - } - } - - # wait on configuration file: - $config = get-content -Path $AGENT_CONF_PATH -ErrorAction Ignore - $regex = 'systemKey\":\"(\w+)\"' - $timeout = 0 - while ([system.string]::IsNullOrEmpty($config)) { - $config = get-content -Path $AGENT_CONF_PATH -ErrorAction Ignore - Write-ToLog -Message:('Waiting for JumpCloud agent config file...') - if ($timeout -eq 20) { - Write-ToLog -Message:('JCAgent could not register the system within the expected window') -Level Error - break - } - Start-Sleep 5 - $timeout += 1 - } - # If config continue to try to get SystemKey; else continue - if ($config) { - # wait on connect key - $systemKey = [regex]::Match($config, $regex).Groups[1].Value - $timeout = 0 - while ([system.string]::IsNullOrEmpty($systemKey)) { - $config = get-content -Path $AGENT_CONF_PATH - $systemKey = [regex]::Match($config, $regex).Groups[1].Value - Write-ToLog -Message:('Waiting for JumpCloud to register the local system...') - if ($timeout -eq 20) { - Write-ToLog -Message:('JCAgent could not register the system within the expected window') -Level Error - break - } - Start-Sleep 5 - $timeout += 1 - } - Write-ToLog -Message:("SystemKey Generated: $($systemKey)") - } - } - Write-ToLog -Message:("Is JumpCloud Agent Installed?: $($agentInstalled)") - if (($agentInstalled) -and (-not [system.string]::IsNullOrEmpty($systemKey)) ) { - Return $true - } else { - Return $false - } -} - -#TODO Add check if library installed on system, else don't import -Add-Type -MemberDefinition @" -[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] -public static extern uint NetApiBufferFree(IntPtr Buffer); -[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] -public static extern int NetGetJoinInformation( - string server, - out IntPtr NameBuffer, - out int BufferType); -"@ -Namespace Win32Api -Name NetApi32 - -function Get-NetBiosName { - $pNameBuffer = [IntPtr]::Zero - $joinStatus = 0 - $apiResult = [Win32Api.NetApi32]::NetGetJoinInformation( - $null, # lpServer - [Ref] $pNameBuffer, # lpNameBuffer - [Ref] $joinStatus # BufferType - ) - if ( $apiResult -eq 0 ) { - [Runtime.InteropServices.Marshal]::PtrToStringAuto($pNameBuffer) - [Void] [Win32Api.NetApi32]::NetApiBufferFree($pNameBuffer) - } -} - -function Convert-Sid { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - $Sid - ) - process { - try { - (New-Object System.Security.Principal.SecurityIdentifier($Sid)).Translate( [System.Security.Principal.NTAccount]).Value - } catch { - return $Sid - } - } -} - -function Convert-UserName { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - $user - ) - process { - try { - (New-Object System.Security.Principal.NTAccount($user)).Translate( [System.Security.Principal.SecurityIdentifier]).Value - } catch { - return $user - } - } -} - -function Test-UsernameOrSID { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - $usernameorsid - ) - Begin { - $sidPattern = "^S-\d-\d+-(\d+-){1,14}\d+$" - $localcomputersidprefix = ((Get-LocalUser | Select-Object -First 1).SID).AccountDomainSID.ToString() - $convertedUser = Convert-UserName $usernameorsid - $registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" - $list = @() - foreach ($profile in $registyProfiles) { - $list += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath - } - $users = @() - foreach ($listItem in $list) { - $isValidFormat = [regex]::IsMatch($($listItem.PSChildName), $sidPattern); - # Get Valid SIDS - if ($isValidFormat) { - $users += [PSCustomObject]@{ - Name = Convert-Sid $listItem.PSChildName - SID = $listItem.PSChildName - } - } - } - } - process { - #check if sid, if valid sid and return sid - if ([regex]::IsMatch($usernameorsid, $sidPattern)) { - if (($usernameorsid -in $users.SID) -And !($users.SID.Contains($localcomputersidprefix))) { - # return, it's a valid SID - Write-ToLog "valid sid returning sid" - return $usernameorsid - } - } elseif ([regex]::IsMatch($convertedUser, $sidPattern)) { - if (($convertedUser -in $users.SID) -And !($users.SID.Contains($localcomputersidprefix))) { - # return, it's a valid SID - Write-ToLog "valid user returning sid" - return $convertedUser - } - } else { - Write-ToLog 'SID or Username is invalid' - throw 'SID or Username is invalid' - } - } -} -#endregion Functions - -#region Agent Install Helper Functions -Function Restart-ComputerWithDelay { - Param( - [int]$TimeOut = 10 - ) - $continue = $true - - while ($continue) { - If ([console]::KeyAvailable) { - Write-Output "Restart Canceled by key press" - Exit; - } Else { - Write-Output "Press any key to cancel... restarting in $TimeOut" -NoNewLine - Start-Sleep -Seconds 1 - $TimeOut = $TimeOut - 1 - Clear-Host - If ($TimeOut -eq 0) { - $continue = $false - $Restart = $true - } - } - } - If ($Restart -eq $True) { - Write-Output "Restarting Computer..." - Restart-Computer -ComputerName $env:COMPUTERNAME -Force - } -} -# Function to validate if NTUser.dat has SYSTEM, Administrators, and the specified user as full control -function Test-DATFilePermission { - param ( - [Parameter(Mandatory = $true)] - [System.String] - $path, - [Parameter(Mandatory = $true)] - [System.String] - $username, - [Parameter(Mandatory = $true)] - [ValidateSet("registry", "ntfs")] - [System.String] - $type - - ) - begin { - $aclUser = "$($Env:ComputerName)\$username" - # ACL naming differs on registry/ ntfs file system, set the correct type - switch ($type) { - 'registry' { - $FilePermissionType = 'RegistryRights' - } - 'ntfs' { - $FilePermissionType = 'FileSystemRights' - } - } - # define empty list - $permissionsHash = @{} - # define required list to test - $requiredAccess = @{ - "NT AUTHORITY\SYSTEM" = @{ - name = "System" - }; - "BUILTIN\Administrators" = @{ - name = "Administrators" - }; - "$($aclUser)" = @{ - name = "$username" - } - } - # Get the path - $ACL = Get-Acl $path - } - process { - # Using AccessControlType to check if it's a deny rule instead of allow since, with NTFS permissions, even if a user/admin is denied, there will still be an allow rule for them and not null - foreach ($requiredRule in $requiredAccess.keys) { - # foreach ($requiredRule in $systemRule, $administratorsRule, $specifiedUserRule) { - # write-ToLog "Begin testing: $($requiredRule)" - $FileACLs = $acl.Access | Where-Object { $_.IdentityReference -eq "$($requiredRule)" } - # write-ToLog "$($requiredRule) access count: $($FileACLs.Count)" - foreach ($fileACL in $FileACLs) { - $rulePermissions = [PSCustomObject]@{ - access = $FileACL.AccessControlType - permissionType = $FileACL.$($FilePermissionType) - identityReference = $FileACL.IdentityReference - ValidPermissions = $true - } - # There will sometimes be multiple FileACLs if an identity is denied access, in which case just break - if ($FileACL.AccessControlType -contains 'Deny') { - $rulePermissions.ValidPermissions = $false - $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null - break - } - # if fullControl access is not grated, just break - if ($FileACL.$($FilePermissionType) -notcontains 'FullControl') { - $rulePermissions.ValidPermissions = $false - $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null - break - } - # else record the access rule and assume it's valid - if ("$($requiredAccess["$($requiredRule)"].name)" -notin $permissionsHash.Keys) { - $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null - } - } - # if the access is not explicitly granted, record the missing value so we can make use of it later - if (-not $FileACLs) { - $rulePermissions = [PSCustomObject]@{ - access = $null - permissionType = $null - identityReference = $requiredRule - ValidPermissions = $false - } - if ("$($requiredAccess["$($requiredRule)"].name)" -notin $permissionsHash.Keys) { - $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null - } - } - } - - } - end { - # if the validPermission block contains any 'false' entries, return false + values, else return true + values - if (($permissionsHash.Values.ValidPermissions -contains $false)) { - return $false, $permissionsHash.Values - } else { - return $true, $permissionsHash.Values - } - } -} -function Set-ADMUScheduledTask { - # Param op "disable" or "enable" then -tasks (array of tasks) - param ( - [Parameter(Mandatory = $true)] - [ValidateSet("disable", "enable")] - [System.String] - $op, - [Parameter(Mandatory = $true)] - [System.Object[]] - $scheduledTasks - ) - - # Switch op - switch ($op) { - "disable" { - try { - $scheduledTasks | ForEach-Object { - Write-ToLog -message:("Disabling Scheduled Task: $($_.TaskName)") - Disable-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath | Out-Null - } - } catch { - Write-ToLog -message:("Failed to disable Scheduled Tasks $($_.Exception.Message)") - } - } - "enable" { - try { - $scheduledTasks | ForEach-Object { - Write-ToLog -message("Enabling Scheduled Task: $($_.TaskName)") - Enable-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath | Out-Null - } - } catch { - Write-ToLog -message("Could not enable Scheduled Task: $($_.TaskName)") -Level Warn - } - } - } -} -#endregion Agent Install Helper Functions - - -##### MIT License ##### -# MIT License - -# Copyright © 2022, Danysys -# Modified by JumpCloud - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# Get user file type associations/FTA -function Get-UserFileTypeAssociation { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true, HelpMessage = 'The SID of the user to capture file type associations')] - [System.String] - $UserSid - ) - $manifestList = @() - # Test path for file type associations - $pathRoot = "HKEY_USERS:\$($UserSid)_admu\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\" - if (Test-Path $pathRoot) { - $exts = Get-ChildItem $pathRoot* - foreach ($ext in $exts) { - $indivExtension = $ext.PSChildName - $progId = (Get-ItemProperty "$($pathRoot)\$indivExtension\UserChoice" -ErrorAction SilentlyContinue).ProgId - $manifestList += [PSCustomObject]@{ - extension = $indivExtension - programId = $progId - } - } - } - return $manifestList -} - -# Get user protocol associations/PTA -function Get-ProtocolTypeAssociation { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true, HelpMessage = 'The SID of the user to capture file type associations')] - [System.String] - $UserSid - ) - $manifestList = @() - - $pathRoot = "HKEY_USERS:\$($UserSid)_admu\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\" - if (Test-Path $pathRoot) { - Get-ChildItem $pathRoot* | - ForEach-Object { - - $progId = (Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue).ProgId - if ($progId) { - $manifestList += [PSCustomObject]@{ - extension = $_.PSChildName - programId = $progId - } - } - } - } - return $manifestList -} -##### END MIT License ##### -function Write-AdmuErrorMessage { - param ( - [string]$ErrorName - ) - switch ($ErrorName) { - "load_unload_error" { - Write-ToLog -Message "Load/Unload Error: The user registry cannot be loaded or unloaded. Verify that the admin running ADMU has permission to the user's NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors" -Level Error - - $Script:ErrorMessage = "Load/Unload Error: User registry cannot be loaded or unloaded. Click the link below for troubleshooting information." - } - "copy_error" { - Write-ToLog -Message:("Registry Copy Error: The user registry files can not be coppied. Verify that the admin running ADMU has permission to the user's NTUser.dat/ UsrClass.dat files, no user processes/ services are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - - $Script:ErrorMessage = "Registry Copy Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." - } - "rename_registry_file_error" { - Write-ToLog -message:("Registry Rename Error: Could not rename user registry file. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - - $Script:ErrorMessage = "Registry Rename Error: Registry files cannot be renamed. Click the link below for troubleshooting information." - } - "backup_error" { - Write-ToLog -Message:("Registry Backup Error: Could not take a backup of the user registry files. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - - $Script:ErrorMessage = "Registry Backup Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." - } - "user_init_error" { - Write-ToLog -Message:("User Initialization Error: The new local user was created but could not be initialized. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - - $Script:ErrorMessage = "User Initialization Error. Click the link below for troubleshooting information." - } - "user_create_error" { - Write-ToLog -Message:("User Creation Error: The new local user could not be created. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - - $Script:ErrorMessage = "User Creation Error. Click the link below for troubleshooting information." - } - "user_folder_redirection_error" { - Write-ToLog -Message:("User Folder Redirection Error: One of the user's main folder (Desktop, Downloads, Documents, Favorites, Pictures, Videos, Music) path is redirected. Verify that the user's main folders path are set to default and not redirected to another path (ie. Network Drive). Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - $Script:ErrorMessage = "User Folder Redirection Error. Click the link below for troubleshooting information." - } - Default { - Write-ToLog -Message:("Error occured, please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error - - $Script:ErrorMessage = "Error occured. Click the link below for troubleshooting information." - } - } -} -# Function to write progress to the progress bar or console -function Write-ToProgress { - param ( - [Parameter(Mandatory = $false)] - $form, - [Parameter(Mandatory = $false)] - $progressBar, - [Parameter(Mandatory = $true)] - $status, - [Parameter(Mandatory = $false)] - $logLevel, - [Parameter(Mandatory = $false)] - $username, - [Parameter(Mandatory = $false)] - $newLocalUsername, - [Parameter(Mandatory = $false)] - $profileSize, - [Parameter(Mandatory = $false)] - $LocalPath - - ) - # Create a hashtable of all status messages - $statusMessages = [ordered]@{ - "Init" = "Initializing Migration" - "Install" = "Installing JumpCloud Agent" - "BackupUserFiles" = "Backing up user profile" - "UserProfileUnit" = "Initializing new user profile" - "BackupRegHive" = "Backing up registry hive" - "VerifyRegHive" = "Verifying registry hive" - "CopyLocalReg" = "Copying local user registry" - "GetACL" = "Getting ACLs" - "CopyUser" = "Copying selected user to new user" - "CopyUserRegFiles" = "Copying user registry files" - "CopyMergedProfile" = "Copying merged profiles to destination profile path" - "CopyDefaultProtocols" = "Copying default protocol associations" - "ValidateUserPermissions" = "Validating user permissions" - "CreateRegEntries" = "Creating registry entries" - "DownloadUWPApps" = "Downloading UWP Apps" - "CheckADStatus" = "Checking AD Status" - "ConversionComplete" = "Profile conversion complete" - "MigrationComplete" = "Migration completed successfully" - } - # If status is error message, write to log - if ($logLevel -eq "Error") { - $statusMessage = $Status - $PercentComplete = 100 - } else { - # Get the status message - $statusMessage = $statusMessages[$status] - # Count the number of status messages - $statusCount = $statusMessages.Count - # Get the index of the status message using for loop - $statusIndex = [array]::IndexOf($statusMessages.Keys, $status) - # Calculate the percentage complete based on the index of the status message - $PercentComplete = ($statusIndex / ($statusCount - 1)) * 100 - } - if ($form) { - if ($username -or $newLocalUsername -or $profileSize -or $LocalPath) { - # Pass in the migration details to the progress bar - Update-ProgressForm -progressBar $progressBar -percentComplete $PercentComplete -Status $statusMessage -username $username -newLocalUsername $newLocalUsername -profileSize $profileSize -localPath $LocalPath - } else { - Update-ProgressForm -progressBar $progressBar -percentComplete $PercentComplete -Status $statusMessage -logLevel $logLevel - } - } else { - Write-Progress -Activity "Migration Progress" -percentComplete $percentComplete -status $statusMessage - } -} - -# Get Profile Size function -function Get-ProfileSize { - param ( - [Parameter(Mandatory = $true)] - [System.String] - $profilePath - ) - $files = Get-ChildItem -Path $profilePath -Recurse -Force | Where-Object { -not $_.PSIsContainer } | Measure-Object -Property Length -Sum - $profileSizeSum = $files.Sum - $totalSizeGB = [math]::round($profileSizeSum / 1GB, 1) - Write-ToLog -Message:("Profile Size: $totalSizeGB GB") - return $totalSizeGB -} - -function Get-DomainStatus { - $ADStatus = dsregcmd.exe /status - foreach ($line in $ADStatus) { - if ($line -match "AzureADJoined : ") { - $AzureADStatus = ($line.trimstart('AzureADJoined : ')) - } - if ($line -match "DomainJoined : ") { - $LocalDomainStatus = ($line.trimstart('DomainJoined : ')) - } - } - # Return both statuses - return $AzureADStatus, $LocalDomainStatus -} - -# Function to validate that the user main folders are default and not redirected -function Test-UserFolderRedirect { - param ( - [Parameter(Mandatory = $true)] - [System.String] - $UserSid - ) - begin { - if ("HKEY_USERS" -notin (Get-psdrive | select-object name).Name) { - Write-ToLog "Mounting HKEY_USERS" - New-PSDrive -Name:("HKEY_USERS") -PSProvider:("Registry") -Root:("HKEY_USERS") | Out-Null - } - $UserFolders = @( "Desktop", "Documents", "Downloads", "Favorites", "Music", "Pictures", "Videos" ) - # Support doc for personal folders: https://support.microsoft.com/en-us/topic/operation-to-change-a-personal-folder-location-fails-in-windows-ffb95139-6dbb-821d-27ec-62c9aaccd720 - $regFoldersPath = "HKEY_USERS:\$($UserSid)_admu\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" - Write-ToLog -Message:("Checking User Shell Folders for USERSID: $($UserSid)") - } - process { - - if (Test-Path -Path $regFoldersPath) { - $redirectedDirectory = $false - # Save all the boolean to a hash table - foreach ($userFolder in $UserFolders) { - switch ($userFolder) { - "Desktop" { - $folderRegKey = "Desktop" - } - "Documents" { - $folderRegKey = "Personal" - } - "Downloads" { - $folderRegKey = "{374DE290-123F-4565-9164-39C4925E467B}" - } - "Favorites" { - $folderRegKey = "Favorites" - } - "Music" { - $folderRegKey = "My Music" - } - "Pictures" { - $folderRegKey = "My Pictures" - } - "Videos" { - $folderRegKey = "My Video" - } - } - # Get the registry value for the user folder - $folderRegKeyValue = (Get-Item -path $regFoldersPath ).GetValue($folderRegKey , '', 'DoNotExpandEnvironmentNames') - $defaultRegFolder = "%USERPROFILE%\$userFolder" - # If the registry value does not match the default path, set redirectedDirectory to true and log the error - if ($folderRegKeyValue -ne $defaultRegFolder) { - Write-ToLog -Message:("$($userFolder) path value: $($folderRegKeyValue) does not match default path - $($defaultRegFolder)") -Level Error - $redirectedDirectory = $true - } else { - Write-ToLog -Message:("User Shell Folder: $($userFolder) is default") - } - } - } else { - # If the registry path does not exist, set redirectedDirectory to true and log the error - Write-ToLog -Message:("User Shell registry folders not found in registry") -Level Error - $redirectedDirectory = $true - } - } - end { - return $redirectedDirectory - } -} Function Start-Migration { [CmdletBinding(HelpURI = "https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/Start-Migration")] Param ( From c5db8222b71641a159f5690a91b3e87d383968e4 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 13:14:39 -0700 Subject: [PATCH 04/73] move form --- jumpcloud-ADMU/Powershell/{ => Private/DisplayForms}/Form.ps1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jumpcloud-ADMU/Powershell/{ => Private/DisplayForms}/Form.ps1 (100%) diff --git a/jumpcloud-ADMU/Powershell/Form.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 similarity index 100% rename from jumpcloud-ADMU/Powershell/Form.ps1 rename to jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 From 08ed3ff902ff0be11e728bfdd690911b3a035ae8 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 13:44:08 -0700 Subject: [PATCH 05/73] Changes for Form --- .../Powershell/Private/DisplayForms/Form.ps1 | 1158 ++++++++--------- 1 file changed, 512 insertions(+), 646 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 index 213a2442..da366e77 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 @@ -1,155 +1,12 @@ -# Hides Powershell Window -$ShowWindowAsync = Add-Type -MemberDefinition @" - [DllImport("user32.dll")] - public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); -"@ -Name "Win32ShowWindowAsync" -Namespace "Win32Functions" -PassThru -# PID of the current process -# Get PID of the current process -$FormWindowPIDHandle = (Get-Process -Id $pid).MainWindowHandle -$ShowWindowAsync::ShowWindowAsync($FormWindowPIDHandle, 0) | Out-Null -# PID -Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Loading ADMU GUI..' -# Base64 Encoded Strings of our Images -$JCLogoBase64 = "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" -$ErrorBase64 = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAFiSURBVHgBpVPNSsNAEJ4ZzwWv/lKfoPUN2oOl3lYKelAoPpkIelBQ4smih8YnMI8QqlQbL0JbT2bH2di0MXFD2n6w7OzMzscwMx/CP3hpqhoBtcWsySlHTmYPEL1Q67vtB8dJ52Dy8dZUZQY8k1ODfPgEur7WcfwM0eueqiJRVzyrUAQMn6x1ffPR8aZEphLN9JwmKR0fQunkKLKHF1cwvLzOkBHqXVMZmbeGOSpJQnJMK4xJvUZLQdzQBWD6GQ2HSCvbpzAIZvbgw0pGsNImZKxAAYTjcU6UZV0Qqrbw9/usCs4lgjLlRcMgQTTKJQJD5EMB6NGXPShbTwz8ZIuHg0SzE43PQKSDE111YRkwqWiz+82Drk1f6/c30d3fb9lo/I3O7U7UbAQ+NesOc1ciEhHx/nJMsKxop+M3DiNAKDBFGZBr/sYkfypKotdQiggVMlRkIvHC+nJcDfp8q+O46Zwfa3qRu77hWMMAAAAASUVORK5CYII=" -$ActiveBase64 = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGKSURBVHgBlZRPTsJQEMbfTBvccgTcKYFQlxJM2hvUEygnID2CJ6jKAZQTgCcoorK1Cf5hZ4/A1oQ3Y98j1FKLbWfTvnn9fp3ON68gcqL15NtsGhfIbLOAxibLITOECPJh0fUmWQ2kF0eB3zBreCcAbPFPsBCR/JbO0vGiP6Bm4FtYM4I4UxflYgVC9rfVwW8lxmsFSAIjks5HzwtRrYyDSpWko46Avq6oPRu6bPK4jIoFR0x0HotVH61kQ0oHCcktC5FA+jOIqb+zpxwGwE6eKA+yPPUi1U9AY5wR2CiArXQOiEfv3cGhuuZBVD9jhxo7mniN2WqkoGt1XfQGl4L5qgiSwNrz2y/e3Uws3SaKIMwcIgl+zOTriEbQfPatMhAdqI8O3edsadjxfOgWQlRFxBM92a2Xm6DofO2FxGYoc3Sz1xjPBYuVqBrMK2WGutUg5QqxdCrBNpC+0iYgFcqlNcqT7DDugUzj6XY+U/8lyHuuPfNdMtEFFp3tmYL4BQQwhbUcvZ1506zmB49h1CYDMPPcAAAAAElFTkSuQmCC" -$BlueBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAElSURBVHgBbVE7TsNAEH2z+dQ+wnKCmIY6nICCIEIXdymIYk4AnCBBEETpVBQWSjgBpEM0+Ag+ghGiQIl3GH/BcUaa1erNe292ZglFuAsLGzMGmwGItCCRZACGh1lvXtAoPYcLjYZ5AbEuDZg8wHRTMfEUtycXCax2kolCzI4dud3kYhcjf5J1GD1NwOyiHq+StqT1B/GhEnK3yjNzGLOPzdqpkhM+bJW7FBGBFMPEEZpNXW+qOgrZNoqwxEXj4SwUu0GNT/yZCIKttl5e7WyZJbUPEfB1BWx9PaebA63wfwbmEPF61cC7H+KgtyeEbBbT/oHisdz61ecYB/f9NyqBc/9K0EvUQ54VO7g7Xaa6Smn4qNFsHclH2cmAst4A7e8lpk45yy8GxWbP/ZW8WwAAAABJRU5ErkJggg==" - -[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") -[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms") -# Add-Type -AssemblyName System.Windows.Forms -Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Windows.Forms, System.Drawing, System.Drawing -function DecodeBase64Image { - param ( - [Parameter(Mandatory = $true)] - [String]$ImageBase64 - ) - # Parameter help description - $ObjBitmapImage = New-Object System.Windows.Media.Imaging.BitmapImage #Provides a specialized BitmapSource that is optimized for loading images using Extensible Application Markup Language (XAML). - $ObjBitmapImage.BeginInit() #Signals the start of the BitmapImage initialization. - $ObjBitmapImage.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($ImageBase64) #Creates a stream whose backing store is memory. - $ObjBitmapImage.EndInit() #Signals the end of the BitmapImage initialization. - $ObjBitmapImage.Freeze() #Makes the current object unmodifiable and sets its IsFrozen property to true. - $ObjBitmapImage -} -function show-mtpSelection { - [OutputType([object[]])] - [CmdletBinding()] - param ( - [Parameter()] - [System.Object] - $Orgs - ) - begin { - $Prompt = 'Please Select A JumpCloud MTP:' - $Title = 'MTP Organization Selection' - # define a data table to store org names/ org ids - $datatable = New-Object system.Data.DataTable - #Define Columns - $col1 = New-Object system.Data.DataColumn "Value", ([string]) - $col2 = New-Object system.Data.DataColumn "Text", ([string]) - #add columns to datatable - $datatable.columns.add($col1) - $datatable.columns.add($col2) - # Define Buttons: - $okButton = [System.Windows.Forms.Button]@{ - Location = '290,12' - Size = '60,22' - Text = 'OK' - DialogResult = [System.Windows.Forms.DialogResult]::OK - } - $cancelButton = [System.Windows.Forms.Button]@{ - Location = '290,40' - Size = '60,22' - Text = 'Cancel' - DialogResult = [System.Windows.Forms.DialogResult]::Cancel - } - # label for the form - $label = [System.Windows.Forms.Label]@{ - AutoSize = $true - Location = '10,10' - Size = '240,20' - MaximumSize = '250,0' - Text = $Prompt - } - $dynamicLabel = [System.Windows.Forms.Label]@{ - AutoSize = $true - Location = '10,30' - Size = '240,20' - MaximumSize = '250,0' - Text = '' - } - foreach ($org in $orgs) { - #Create a row - $name = New-Variable -Name "row_$($org._id)" - $name = $datarow1 = $datatable.NewRow() - #Enter data in the row - $name.Text = "$($org.DisplayName)" - $name.Value = "$($org._id)" - #Add the row to the datatable - $datatable.Rows.Add($name) - } - #create a combobox - $comboBox = [System.Windows.Forms.ComboBox]@{ - Location = '10,90' - AutoSize = $true - MaximumSize = '500,0' - # MaximumSize = '335,0' - DropDownStyle = "DropDownList" - } - $SelectBox = [System.Windows.Forms.Form]@{ - Text = $Title - Size = '369,159' - # Size = '369,159' - StartPosition = 'CenterScreen' - AcceptButton = $okButton - CancelButton = $cancelButton - FormBorderStyle = 'FixedDialog' - MinimizeBox = $false - MaximizeBox = $false - } - } - process { - #clear combo before we bind it - $combobox.Items.Clear() - - #bind combobox to datatable - $combobox.ValueMember = "Value" - $combobox.DisplayMember = "Text" - $combobox.Datasource = $datatable +Function Show-SelectionForm { - #add combobox to form - $SelectBox.Controls.Add($combobox) + # Set source here. Take note in the XAML as to where the variable name was taken. - #show form - $SelectBox.Controls.AddRange(@($okButton, $cancelButton, $label, $dynamicLabel)) - $SelectBox.Topmost = $true - $SelectBox.Add_Shown({ $comboBox.Select() }) - - } - end { - $combobox.Add_SelectedIndexChanged({ - #output the selected value and text - $dynamicLabel.Text = "OrgName: $($combobox.SelectedItem['Text'])" - $dynamicLabel.Refresh(); - # write-host $combobox.SelectedItem["Value"] $combobox.SelectedItem["Text"] - }) - $result = $SelectBox.ShowDialog() - if ($result -eq [System.Windows.Forms.DialogResult]::OK) { - # return id of the org we selected - return $combobox.SelectedItem["Value"], $combobox.SelectedItem["Text"] - } else { - return $null - } - } -} -# Set source here. Take note in the XAML as to where the variable name was taken. - -#============================================================================================== -# XAML Code - Imported from Visual Studio WPF Application -#============================================================================================== -[void][System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework') -[xml]$XAML = @' + #============================================================================================== + # XAML Code - Imported from Visual Studio WPF Application + #============================================================================================== + [void][System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework') + [xml]$XAML = @' '@ -# Read XAML -$reader = (New-Object System.Xml.XmlNodeReader $xaml) -Try { - $Form = [Windows.Markup.XamlReader]::Load($reader) -} Catch { - Write-Error "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; - Exit; -} -#=========================================================================== -# Store Form Objects In PowerShell -#=========================================================================== -$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object { - New-Variable -Name $_.Name -Value $Form.FindName($_.Name) -Force -} -$JCLogoImg.Source = DecodeBase64Image -ImageBase64 $JCLogoBase64 -$img_ckey_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 -$img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 -$img_apikey_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 -$img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 -$img_localaccount_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 -$img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 -$img_localaccount_password_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 -$img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 -# Define misc static variables - -$WmiComputerSystem = Get-WmiObject -Class:('Win32_ComputerSystem') -Write-progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Checking AzureAD Status..' -PercentComplete 25 -Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Checking AzureAD Status..' -if ($WmiComputerSystem.PartOfDomain) { - $WmiComputerDomain = Get-WmiObject -Class:('Win32_ntDomain') - $securechannelstatus = Test-ComputerSecureChannel - - $nbtstat = nbtstat -n - foreach ($line in $nbtStat) { - if ($line -match '^\s*([^<\s]+)\s*<00>\s*GROUP') { - $NetBiosName = $matches[1] - } + # Read XAML + $reader = (New-Object System.Xml.XmlNodeReader $xaml) + Try { + $Form = [Windows.Markup.XamlReader]::Load($reader) + } Catch { + Write-Error "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; + Exit; } - - if ([System.String]::IsNullOrEmpty($WmiComputerDomain[0].DnsForestName) -and $securechannelstatus -eq $false) { - $DomainName = 'Fix Secure Channel' - } else { - $DomainName = [string]$WmiComputerDomain.DnsForestName + #=========================================================================== + # Store Form Objects In PowerShell + #=========================================================================== + $xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object { + New-Variable -Name $_.Name -Value $Form.FindName($_.Name) -Force } - $NetBiosName = [string]$NetBiosName -} elseif ($WmiComputerSystem.PartOfDomain -eq $false) { - $DomainName = 'N/A' - $NetBiosName = 'N/A' - $securechannelstatus = 'N/A' -} -if ((Get-CimInstance Win32_OperatingSystem).Version -match '10') { - $AzureADInfo = dsregcmd.exe /status - foreach ($line in $AzureADInfo) { - if ($line -match "AzureADJoined : ") { - $AzureADStatus = ($line.trimstart('AzureADJoined : ')) - } - if ($line -match "WorkplaceJoined : ") { - $Workplace_join = ($line.trimstart('WorkplaceJoined : ')) - } - if ($line -match "TenantName : ") { - $TenantName = ($line.trimstart('WorkplaceTenantName : ')) - } - if ($line -match "DomainJoined : ") { - $AzureDomainStatus = ($line.trimstart('DomainJoined : ')) + $JCLogoImg.Source = DecodeBase64Image -ImageBase64 $JCLogoBase64 + $img_ckey_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 + $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_apikey_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 + $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_localaccount_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 + $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_localaccount_password_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 + $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + # Define misc static variables + + $WmiComputerSystem = Get-WmiObject -Class:('Win32_ComputerSystem') + Write-progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Checking AzureAD Status..' -PercentComplete 25 + Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Checking AzureAD Status..' + if ($WmiComputerSystem.PartOfDomain) { + $WmiComputerDomain = Get-WmiObject -Class:('Win32_ntDomain') + $securechannelstatus = Test-ComputerSecureChannel + + $nbtstat = nbtstat -n + foreach ($line in $nbtStat) { + if ($line -match '^\s*([^<\s]+)\s*<00>\s*GROUP') { + $NetBiosName = $matches[1] + } } - } -} else { - $AzureADStatus = 'N/A' - $Workplace_join = 'N/A' - $TenantName = 'N/A' -} - -$FormResults = [PSCustomObject]@{ } -Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Verifying Local Accounts & Group Membership..' -PercentComplete 50 -Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Verifying Local Accounts & Group Membership..' -Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Getting C:\ & Local Profile Data..' -PercentComplete 70 -Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Getting C:\ & Local Profile Data..' -# Get Valid SIDs from the Registry and build user object -$registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" -$profileList = @() -foreach ($profile in $registyProfiles) { - $profileList += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath -} -# List to store users -$users = @() -foreach ($listItem in $profileList) { - $sidPattern = "^S-\d-\d+-(\d+-){1,14}\d+$" - $isValidFormat = [regex]::IsMatch($($listItem.PSChildName), $sidPattern); - # Get Valid SIDs - if ($isValidFormat) { - # Populate Users List - $users += [PSCustomObject]@{ - Name = Convert-Sid $listItem.PSChildName - LocalPath = $listItem.ProfileImagePath - SID = $listItem.PSChildName - IsLocalAdmin = $null - LocalProfileSize = $null - Loaded = $null - RoamingConfigured = $null - LastLogin = $null + + if ([System.String]::IsNullOrEmpty($WmiComputerDomain[0].DnsForestName) -and $securechannelstatus -eq $false) { + $DomainName = 'Fix Secure Channel' + } else { + $DomainName = [string]$WmiComputerDomain.DnsForestName } + $NetBiosName = [string]$NetBiosName + } elseif ($WmiComputerSystem.PartOfDomain -eq $false) { + $DomainName = 'N/A' + $NetBiosName = 'N/A' + $securechannelstatus = 'N/A' } -} -# Get Win32 Profiles to merge data with valid SIDs -$win32UserProfiles = Get-WmiObject -Class:('Win32_UserProfile') -Property * | Where-Object { $_.Special -eq $false } -$date_format = "yyyy-MM-dd HH:mm" -foreach ($user in $users) { - # Get Data from Win32Profile - foreach ($win32user in $win32UserProfiles) { - if ($($user.SID) -eq $($win32user.SID)) { - $user.RoamingConfigured = $win32user.RoamingConfigured - $user.Loaded = $win32user.Loaded - if ([string]::IsNullOrEmpty($($win32user.LastUseTime))) { - $user.LastLogin = "N/A" - } else { - $user.LastLogin = [System.Management.ManagementDateTimeConverter]::ToDateTime($($win32user.LastUseTime)).ToUniversalTime().ToSTring($date_format) + if ((Get-CimInstance Win32_OperatingSystem).Version -match '10') { + $AzureADInfo = dsregcmd.exe /status + foreach ($line in $AzureADInfo) { + if ($line -match "AzureADJoined : ") { + $AzureADStatus = ($line.trimstart('AzureADJoined : ')) + } + if ($line -match "WorkplaceJoined : ") { + $Workplace_join = ($line.trimstart('WorkplaceJoined : ')) + } + if ($line -match "TenantName : ") { + $TenantName = ($line.trimstart('WorkplaceTenantName : ')) + } + if ($line -match "DomainJoined : ") { + $AzureDomainStatus = ($line.trimstart('DomainJoined : ')) } } - } - # Get Admin Status - try { - $admin = Get-LocalGroupMember -Member "$($user.SID)" -Name "Administrators" -EA SilentlyContinue - } catch { - $user = Get-LocalGroupMember -Member "$($user.SID)" -Name "Users" - } - if ($admin) { - $user.IsLocalAdmin = $true } else { - $user.IsLocalAdmin = $false - } - # Get Profile Size - # $largeprofile = Get-ChildItem $($user.LocalPath) -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Sum length | Select-Object -ExpandProperty Sum - # $largeprofile = [math]::Round($largeprofile / 1MB, 0) - # $user.LocalProfileSize = $largeprofile -} - -Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Building Profile Group Box Query..' -PercentComplete 85 -Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Building Profile Group Box Query..' - -$Profiles = $users | Select-Object SID, RoamingConfigured, Loaded, IsLocalAdmin, LocalPath, LocalProfileSize, LastLogin, @{Name = "UserName"; EXPRESSION = { $_.Name } } -Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Done!' -PercentComplete 100 -Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Done!' - -#load UI Labels - -#SystemInformation -$lbComputerName.Content = $WmiComputerSystem.Name - -#DomainInformation -$lbDomainName.Content = $DomainName -$lbNetBios.Content = $NetBiosName - -#AzureADInformation -$lbAzureAD_Joined.Content = $AzureADStatus -$lbTenantName.Content = $TenantName -Function Test-Button([object]$tbJumpCloudUserName, [object]$tbJumpCloudConnectKey, [object]$tbTempPassword, [object]$lvProfileList, [object]$tbJumpCloudAPIKey) { - If (![System.String]::IsNullOrEmpty($lvProfileList.SelectedItem.UserName)) { - If (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20) ` - -and ((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password) -and ($cb_installjcagent.IsChecked -eq $true))` - -and (!(Test-IsNotEmpty $tbJumpCloudAPIKey.Password) -and ($cb_autobindjcuser.IsChecked -eq $true))` - -and ((Test-CharLen -len 24 -testString $Env:selectedOrgID) -and (Test-HasNoSpace $Env:selectedOrgID) -and ($cb_autobindjcuser.IsChecked -eq $true))` - -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` - -and !(($($lvProfileList.selectedItem.Username) -split '\\')[0] -match $WmiComputerSystem.Name)` - -and !(Test-Localusername $tbJumpCloudUserName.Text)) { - $script:bMigrateProfile.Content = "Migrate Profile" - $script:bMigrateProfile.IsEnabled = $true - Return $true - } ElseIf (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20) ` - -and ((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password) -and ($cb_installjcagent.IsChecked -eq $true) -and ($cb_autobindjcuser.IsChecked -eq $false))` - -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` - -and !(Test-Localusername $tbJumpCloudUserName.Text)) { - $script:bMigrateProfile.Content = "Migrate Profile" - $script:bMigrateProfile.IsEnabled = $true - Return $true - } ElseIf (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20) ` - -and (!(Test-IsNotEmpty $tbJumpCloudAPIKey.Password) -and ($cb_autobindjcuser.IsChecked -eq $true) -and ($cb_installjcagent.IsChecked -eq $false))` - -and ((Test-CharLen -len 24 -testString $Env:selectedOrgID) -and (Test-HasNoSpace $Env:selectedOrgID) -and ($cb_autobindjcuser.IsChecked -eq $true) -and ($cb_installjcagent.IsChecked -eq $false))` - -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` - -and !(Test-Localusername $tbJumpCloudUserName.Text)) { - $script:bMigrateProfile.Content = "Migrate Profile" - $script:bMigrateProfile.IsEnabled = $true - Return $true - } Elseif (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20)` - -and ($cb_installjcagent.IsChecked -eq $false) -and ($cb_autobindjcuser.IsChecked -eq $false)` - -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` - -and !($lvProfileList.selectedItem.Username -match $WmiComputerSystem.Name)` - -and !(Test-Localusername $tbJumpCloudUserName.Text)) { - $script:bMigrateProfile.Content = "Migrate Profile" - $script:bMigrateProfile.IsEnabled = $true - Return $true - } Elseif ($lvProfileList.selectedItem.Username -eq 'UNKNOWN ACCOUNT') { - # Unmatched Profile, prevent migration - $script:bMigrateProfile.IsEnabled = $false - Return $false - } elseif (($($lvProfileList.selectedItem.Username) -split '\\')[0] -match $WmiComputerSystem.Name) { - $script:bMigrateProfile.IsEnabled = $false - Return $false - } Else { - $script:bMigrateProfile.Content = "Migrate Profile" - $script:bMigrateProfile.IsEnabled = $false - Return $false - } - } Else { - $script:bMigrateProfile.IsEnabled = $false - Return $false + $AzureADStatus = 'N/A' + $Workplace_join = 'N/A' + $TenantName = 'N/A' } -} - -## Form changes & interactions - -# Install JCAgent checkbox -$script:InstallJCAgent = $false -$cb_installjcagent.Add_Checked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) -$cb_installjcagent.Add_Checked( { $script:InstallJCAgent = $true }) -$cb_installjcagent.Add_Checked( { $tbJumpCloudConnectKey.IsEnabled = $true }) -$cb_installjcagent.Add_Checked( { $img_ckey_info.Visibility = 'Visible' }) -$cb_installjcagent.Add_Checked( { $img_ckey_valid.Visibility = 'Visible' }) -$cb_installjcagent.Add_Checked( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password)) -eq $false) { - #$tbJumpCloudConnectKey.Tooltip = "Connect Key Must be 40chars & Not Contain Spaces" - $tbJumpCloudConnectKey.Background = "#FFC6CBCF" - $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" - } Else { - $tbJumpCloudConnectKey.Background = "white" - $tbJumpCloudConnectKey.Tooltip = $null - $tbJumpCloudConnectKey.FontWeight = "Normal" - $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" - } - }) - -$cb_installjcagent.Add_UnChecked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) -$cb_installjcagent.Add_Unchecked( { $script:InstallJCAgent = $false }) -$cb_installjcagent.Add_Unchecked( { $tbJumpCloudConnectKey.IsEnabled = $false }) -$cb_installjcagent.Add_Unchecked( { $img_ckey_info.Visibility = 'Hidden' }) -$cb_installjcagent.Add_Unchecked( { $img_ckey_valid.Visibility = 'Hidden' }) -$cb_installjcagent.Add_Unchecked( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password) -or ($cb_installjcagent.IsEnabled)) -eq $false) { - #$tbJumpCloudConnectKey.Tooltip = "Connect Key Must be 40chars & Not Contain Spaces" - $tbJumpCloudConnectKey.Background = "#FFC6CBCF" - $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" - } Else { - $tbJumpCloudConnectKey.Background = "white" - $tbJumpCloudConnectKey.Tooltip = $null - $tbJumpCloudConnectKey.FontWeight = "Normal" - $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" - } - }) - - -# Autobind JC User checkbox -$script:AutobindJCUser = $false -$cb_autobindjcuser.Add_Checked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) -$cb_autobindjcuser.Add_Checked( { $script:AutobindJCUser = $true }) -$cb_autobindjcuser.Add_Checked( { $tbJumpCloudAPIKey.IsEnabled = $true }) -$cb_autobindjcuser.Add_Checked( { $img_apikey_info.Visibility = 'Visible' }) -$cb_autobindjcuser.Add_Checked( { $img_apikey_valid.Visibility = 'Visible' }) -$cb_autobindjcuser.Add_Checked( { $cb_bindAsAdmin.IsEnabled = $true }) -$cb_bindAsAdmin.Add_Checked( { $script:BindAsAdmin = $true }) -$cb_autobindjcuser.Add_Checked( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If (Test-IsNotEmpty $tbJumpCloudAPIKey.Password ) { - #$tbJumpCloudAPIKey.Tooltip = "API Key Must be 40chars & Not Contain Spaces" - $tbJumpCloudAPIKey.Background = "#FFC6CBCF" - $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" - } Else { - $tbJumpCloudAPIKey.Background = "white" - $tbJumpCloudAPIKey.Tooltip = $null - $tbJumpCloudAPIKey.FontWeight = "Normal" - $tbJumpCloudAPIKey.BorderBrush = "#FFC6CBCF" + $FormResults = [PSCustomObject]@{ } + Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Verifying Local Accounts & Group Membership..' -PercentComplete 50 + Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Verifying Local Accounts & Group Membership..' + Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Getting C:\ & Local Profile Data..' -PercentComplete 70 + Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Getting C:\ & Local Profile Data..' + # Get Valid SIDs from the Registry and build user object + $registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + $profileList = @() + foreach ($profile in $registyProfiles) { + $profileList += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath + } + # List to store users + $users = @() + foreach ($listItem in $profileList) { + $sidPattern = "^S-\d-\d+-(\d+-){1,14}\d+$" + $isValidFormat = [regex]::IsMatch($($listItem.PSChildName), $sidPattern); + # Get Valid SIDs + if ($isValidFormat) { + # Populate Users List + $users += [PSCustomObject]@{ + Name = Convert-SecurityIdentifier $listItem.PSChildName + LocalPath = $listItem.ProfileImagePath + SID = $listItem.PSChildName + IsLocalAdmin = $null + LocalProfileSize = $null + Loaded = $null + RoamingConfigured = $null + LastLogin = $null + } } - }) - - -$cb_autobindjcuser.Add_UnChecked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) -$cb_autobindjcuser.Add_Unchecked( { $script:AutobindJCUser = $false }) -$cb_autobindjcuser.Add_Unchecked( { $tbJumpCloudAPIKey.IsEnabled = $false }) -$cb_autobindjcuser.Add_Unchecked( { $img_apikey_info.Visibility = 'Hidden' }) -$cb_autobindjcuser.Add_Unchecked( { $img_apikey_valid.Visibility = 'Hidden' }) -$cb_autobindjcuser.Add_Unchecked( { $cb_bindAsAdmin.IsEnabled = $false }) -$cb_autobindjcuser.Add_Unchecked( { $cb_bindAsAdmin.IsChecked = $false }) -$cb_bindAsAdmin.Add_Unchecked( { $script:BindAsAdmin = $false }) -$cb_autobindjcuser.Add_Unchecked( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If ((!(Test-IsNotEmpty $tbJumpCloudAPIKey.Password) -or ($cb_autobindjcuser.IsEnabled)) -eq $false) { - #$tbJumpCloudAPIKey.Tooltip = "API Key Must be 40chars & Not Contain Spaces" - $tbJumpCloudAPIKey.Background = "#FFC6CBCF" - $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" - } Else { - $tbJumpCloudAPIKey.Background = "white" - $tbJumpCloudAPIKey.Tooltip = $null - $tbJumpCloudAPIKey.FontWeight = "Normal" - $tbJumpCloudAPIKey.BorderBrush = "#FFC6CBCF" + } + # Get Win32 Profiles to merge data with valid SIDs + $win32UserProfiles = Get-WmiObject -Class:('Win32_UserProfile') -Property * | Where-Object { $_.Special -eq $false } + $date_format = "yyyy-MM-dd HH:mm" + foreach ($user in $users) { + # Get Data from Win32Profile + foreach ($win32user in $win32UserProfiles) { + if ($($user.SID) -eq $($win32user.SID)) { + $user.RoamingConfigured = $win32user.RoamingConfigured + $user.Loaded = $win32user.Loaded + if ([string]::IsNullOrEmpty($($win32user.LastUseTime))) { + $user.LastLogin = "N/A" + } else { + $user.LastLogin = [System.Management.ManagementDateTimeConverter]::ToDateTime($($win32user.LastUseTime)).ToUniversalTime().ToSTring($date_format) + } + } } - }) - - -# Leave Domain checkbox -if (($AzureADStatus -eq 'Yes') -or ($AzureDomainStatus -eq 'Yes')) { - $script:cb_leavedomain.IsEnabled = $true -} else { - Write-ToLog "Device is not AzureAD Joined or Domain Joined. Leave Domain Checkbox Disabled." - $script:cb_leavedomain.IsEnabled = $false -} -$script:LeaveDomain = $false -$cb_leavedomain.Add_Checked( { $script:LeaveDomain = $true }) -$cb_leavedomain.Add_Unchecked( { $script:LeaveDomain = $false }) - -# Force Reboot checkbox -$script:ForceReboot = $false -$cb_forcereboot.Add_Checked( { $script:ForceReboot = $true }) -$cb_forcereboot.Add_Unchecked( { $script:ForceReboot = $false }) - -$hostname = $env:computername -$tbJumpCloudUserName.add_TextChanged( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If ((Test-IsNotEmpty $tbJumpCloudUserName.Text) -or (!(Test-HasNoSpace $tbJumpCloudUserName.Text)) -or (Test-Localusername $tbJumpCloudUserName.Text) -or (($tbJumpCloudUserName.Text).Length -gt 20) -or $tbJumpCloudUserName.Text -eq $hostname) { - $tbJumpCloudUserName.Background = "#FFC6CBCF" - $tbJumpCloudUserName.BorderBrush = "#FFF90000" - $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_localaccount_valid.ToolTip = "Local account username can't be empty, contain spaces, already exist on the local system or match the local computer name. Username must only be 20 characters long" - } Else { - $tbJumpCloudUserName.Background = "white" - $tbJumpCloudUserName.FontWeight = "Normal" - $tbJumpCloudUserName.BorderBrush = "#FFC6CBCF" - $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 - $img_localaccount_valid.ToolTip = $null + # Get Admin Status + try { + $admin = Get-LocalGroupMember -Member "$($user.SID)" -Name "Administrators" -EA SilentlyContinue + } catch { + $user = Get-LocalGroupMember -Member "$($user.SID)" -Name "Users" } - if ($tbJumpCloudUserName.Text -eq $hostname) { - Write-ToLog "JumpCloud Username can not be the same as the hostname" - $script:bMigrateProfile.IsEnabled = $false - $img_localaccount_valid.ToolTip = "JumpCloud Username can not be the same as the hostname. Please change the username." + if ($admin) { + $user.IsLocalAdmin = $true + } else { + $user.IsLocalAdmin = $false } - }) - -$tbJumpCloudConnectKey.Add_PasswordChanged( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password)) -eq $false) { - $tbJumpCloudConnectKey.Background = "#FFC6CBCF" - $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" - $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_ckey_valid.ToolTip = "Connect Key must be 40chars & not contain spaces." + # Get Profile Size + # $largeprofile = Get-ChildItem $($user.LocalPath) -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Sum length | Select-Object -ExpandProperty Sum + # $largeprofile = [math]::Round($largeprofile / 1MB, 0) + # $user.LocalProfileSize = $largeprofile + } + + Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Building Profile Group Box Query..' -PercentComplete 85 + Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Building Profile Group Box Query..' + + $Profiles = $users | Select-Object SID, RoamingConfigured, Loaded, IsLocalAdmin, LocalPath, LocalProfileSize, LastLogin, @{Name = "UserName"; EXPRESSION = { $_.Name } } + Write-Progress -Activity 'Jumpcloud ADMU' -Status 'Loading Jumpcloud ADMU. Please Wait.. Done!' -PercentComplete 100 + Write-ToLog 'Loading Jumpcloud ADMU. Please Wait.. Done!' + + #load UI Labels + + #SystemInformation + $lbComputerName.Content = $WmiComputerSystem.Name + + #DomainInformation + $lbDomainName.Content = $DomainName + $lbNetBios.Content = $NetBiosName + + #AzureADInformation + $lbAzureAD_Joined.Content = $AzureADStatus + $lbTenantName.Content = $TenantName + Function Test-Button([object]$tbJumpCloudUserName, [object]$tbJumpCloudConnectKey, [object]$tbTempPassword, [object]$lvProfileList, [object]$tbJumpCloudAPIKey) { + If (![System.String]::IsNullOrEmpty($lvProfileList.SelectedItem.UserName)) { + If (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20) ` + -and ((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password) -and ($cb_installjcagent.IsChecked -eq $true))` + -and (!(Test-IsNotEmpty $tbJumpCloudAPIKey.Password) -and ($cb_autobindjcuser.IsChecked -eq $true))` + -and ((Test-CharLen -len 24 -testString $Env:selectedOrgID) -and (Test-HasNoSpace $Env:selectedOrgID) -and ($cb_autobindjcuser.IsChecked -eq $true))` + -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` + -and !(($($lvProfileList.selectedItem.Username) -split '\\')[0] -match $WmiComputerSystem.Name)` + -and !(Test-Localusername $tbJumpCloudUserName.Text)) { + $bMigrateProfile.Content = "Migrate Profile" + $bMigrateProfile.IsEnabled = $true + Return $true + } ElseIf (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20) ` + -and ((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password) -and ($cb_installjcagent.IsChecked -eq $true) -and ($cb_autobindjcuser.IsChecked -eq $false))` + -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` + -and !(Test-Localusername $tbJumpCloudUserName.Text)) { + $bMigrateProfile.Content = "Migrate Profile" + $bMigrateProfile.IsEnabled = $true + Return $true + } ElseIf (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20) ` + -and (!(Test-IsNotEmpty $tbJumpCloudAPIKey.Password) -and ($cb_autobindjcuser.IsChecked -eq $true) -and ($cb_installjcagent.IsChecked -eq $false))` + -and ((Test-CharLen -len 24 -testString $Env:selectedOrgID) -and (Test-HasNoSpace $Env:selectedOrgID) -and ($cb_autobindjcuser.IsChecked -eq $true) -and ($cb_installjcagent.IsChecked -eq $false))` + -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` + -and !(Test-Localusername $tbJumpCloudUserName.Text)) { + $bMigrateProfile.Content = "Migrate Profile" + $bMigrateProfile.IsEnabled = $true + Return $true + } Elseif (!(Test-IsNotEmpty $tbJumpCloudUserName.Text) -and (Test-HasNoSpace $tbJumpCloudUserName.Text) -and (($($tbJumpCloudUserName.Text).length) -le 20)` + -and ($cb_installjcagent.IsChecked -eq $false) -and ($cb_autobindjcuser.IsChecked -eq $false)` + -and !(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)` + -and !($lvProfileList.selectedItem.Username -match $WmiComputerSystem.Name)` + -and !(Test-Localusername $tbJumpCloudUserName.Text)) { + $bMigrateProfile.Content = "Migrate Profile" + $bMigrateProfile.IsEnabled = $true + Return $true + } Elseif ($lvProfileList.selectedItem.Username -eq 'UNKNOWN ACCOUNT') { + # Unmatched Profile, prevent migration + $bMigrateProfile.IsEnabled = $false + Return $false + } elseif (($($lvProfileList.selectedItem.Username) -split '\\')[0] -match $WmiComputerSystem.Name) { + $bMigrateProfile.IsEnabled = $false + Return $false + } Else { + $bMigrateProfile.Content = "Migrate Profile" + $bMigrateProfile.IsEnabled = $false + Return $false + } } Else { - $tbJumpCloudConnectKey.Background = "white" - $tbJumpCloudConnectKey.FontWeight = "Normal" - $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" - $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 - $img_ckey_valid.ToolTip = $null + $bMigrateProfile.IsEnabled = $false + Return $false } - }) + } -$tbJumpCloudAPIKey.Add_PasswordChanged( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If (Test-IsNotEmpty $tbJumpCloudAPIKey.Password) { - $tbJumpCloudAPIKey.Background = "#FFC6CBCF" - $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_apikey_valid.ToolTip = "Please enter a valid JumpCloud API Key" + ## Form changes & interactions + + # Install JCAgent checkbox + $InstallJCAgent = $false + $cb_installjcagent.Add_Checked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) + $cb_installjcagent.Add_Checked( { $InstallJCAgent = $true }) + $cb_installjcagent.Add_Checked( { $tbJumpCloudConnectKey.IsEnabled = $true }) + $cb_installjcagent.Add_Checked( { $img_ckey_info.Visibility = 'Visible' }) + $cb_installjcagent.Add_Checked( { $img_ckey_valid.Visibility = 'Visible' }) + $cb_installjcagent.Add_Checked( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password)) -eq $false) { + #$tbJumpCloudConnectKey.Tooltip = "Connect Key Must be 40chars & Not Contain Spaces" + $tbJumpCloudConnectKey.Background = "#FFC6CBCF" + $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" + } Else { + $tbJumpCloudConnectKey.Background = "white" + $tbJumpCloudConnectKey.Tooltip = $null + $tbJumpCloudConnectKey.FontWeight = "Normal" + $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" + } - } Else { - # Get org name/ id - try { - $OrgSelection = Get-mtpOrganization -ApiKey $tbJumpCloudAPIKey.Password -inputType #-errorAction silentlycontinue - $lbl_orgName.Text = "$($OrgSelection[1])" - $Env:selectedOrgID = "$($OrgSelection[0])" + }) + + $cb_installjcagent.Add_UnChecked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) + $cb_installjcagent.Add_Unchecked( { $InstallJCAgent = $false }) + $cb_installjcagent.Add_Unchecked( { $tbJumpCloudConnectKey.IsEnabled = $false }) + $cb_installjcagent.Add_Unchecked( { $img_ckey_info.Visibility = 'Hidden' }) + $cb_installjcagent.Add_Unchecked( { $img_ckey_valid.Visibility = 'Hidden' }) + $cb_installjcagent.Add_Unchecked( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password) -or ($cb_installjcagent.IsEnabled)) -eq $false) { + #$tbJumpCloudConnectKey.Tooltip = "Connect Key Must be 40chars & Not Contain Spaces" + $tbJumpCloudConnectKey.Background = "#FFC6CBCF" + $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" + } Else { + $tbJumpCloudConnectKey.Background = "white" + $tbJumpCloudConnectKey.Tooltip = $null + $tbJumpCloudConnectKey.FontWeight = "Normal" + $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" + } + }) + + + # Autobind JC User checkbox + $AutobindJCUser = $false + $cb_autobindjcuser.Add_Checked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) + $cb_autobindjcuser.Add_Checked( { $AutobindJCUser = $true }) + $cb_autobindjcuser.Add_Checked( { $tbJumpCloudAPIKey.IsEnabled = $true }) + $cb_autobindjcuser.Add_Checked( { $img_apikey_info.Visibility = 'Visible' }) + $cb_autobindjcuser.Add_Checked( { $img_apikey_valid.Visibility = 'Visible' }) + $cb_autobindjcuser.Add_Checked( { $cb_bindAsAdmin.IsEnabled = $true }) + $cb_bindAsAdmin.Add_Checked( { $BindAsAdmin = $true }) + $cb_autobindjcuser.Add_Checked( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If (Test-IsNotEmpty $tbJumpCloudAPIKey.Password ) { + #$tbJumpCloudAPIKey.Tooltip = "API Key Must be 40chars & Not Contain Spaces" + $tbJumpCloudAPIKey.Background = "#FFC6CBCF" + $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" + } Else { $tbJumpCloudAPIKey.Background = "white" $tbJumpCloudAPIKey.Tooltip = $null $tbJumpCloudAPIKey.FontWeight = "Normal" $tbJumpCloudAPIKey.BorderBrush = "#FFC6CBCF" - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 - $img_apikey_valid.ToolTip = $null - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - } catch { - $script:bMigrateProfile.IsEnabled = $false + } + }) + + + $cb_autobindjcuser.Add_UnChecked( { Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) }) + $cb_autobindjcuser.Add_Unchecked( { $AutobindJCUser = $false }) + $cb_autobindjcuser.Add_Unchecked( { $tbJumpCloudAPIKey.IsEnabled = $false }) + $cb_autobindjcuser.Add_Unchecked( { $img_apikey_info.Visibility = 'Hidden' }) + $cb_autobindjcuser.Add_Unchecked( { $img_apikey_valid.Visibility = 'Hidden' }) + $cb_autobindjcuser.Add_Unchecked( { $cb_bindAsAdmin.IsEnabled = $false }) + $cb_autobindjcuser.Add_Unchecked( { $cb_bindAsAdmin.IsChecked = $false }) + $cb_bindAsAdmin.Add_Unchecked( { $BindAsAdmin = $false }) + $cb_autobindjcuser.Add_Unchecked( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If ((!(Test-IsNotEmpty $tbJumpCloudAPIKey.Password) -or ($cb_autobindjcuser.IsEnabled)) -eq $false) { + #$tbJumpCloudAPIKey.Tooltip = "API Key Must be 40chars & Not Contain Spaces" + $tbJumpCloudAPIKey.Background = "#FFC6CBCF" + $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" + } Else { + $tbJumpCloudAPIKey.Background = "white" + $tbJumpCloudAPIKey.Tooltip = $null + $tbJumpCloudAPIKey.FontWeight = "Normal" + $tbJumpCloudAPIKey.BorderBrush = "#FFC6CBCF" + } + }) + + + # Leave Domain checkbox + if (($AzureADStatus -eq 'Yes') -or ($AzureDomainStatus -eq 'Yes')) { + $cb_leavedomain.IsEnabled = $true + } else { + Write-ToLog "Device is not AzureAD Joined or Domain Joined. Leave Domain Checkbox Disabled." + $cb_leavedomain.IsEnabled = $false + } + $LeaveDomain = $false + $cb_leavedomain.Add_Checked( { $LeaveDomain = $true }) + $cb_leavedomain.Add_Unchecked( { $LeaveDomain = $false }) + + # Force Reboot checkbox + $ForceReboot = $false + $cb_forcereboot.Add_Checked( { $ForceReboot = $true }) + $cb_forcereboot.Add_Unchecked( { $ForceReboot = $false }) + + $hostname = $env:computername + $tbJumpCloudUserName.add_TextChanged( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If ((Test-IsNotEmpty $tbJumpCloudUserName.Text) -or (!(Test-HasNoSpace $tbJumpCloudUserName.Text)) -or (Test-Localusername $tbJumpCloudUserName.Text) -or (($tbJumpCloudUserName.Text).Length -gt 20) -or $tbJumpCloudUserName.Text -eq $hostname) { + $tbJumpCloudUserName.Background = "#FFC6CBCF" + $tbJumpCloudUserName.BorderBrush = "#FFF90000" + $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_localaccount_valid.ToolTip = "Local account username can't be empty, contain spaces, already exist on the local system or match the local computer name. Username must only be 20 characters long" + } Else { + $tbJumpCloudUserName.Background = "white" + $tbJumpCloudUserName.FontWeight = "Normal" + $tbJumpCloudUserName.BorderBrush = "#FFC6CBCF" + $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_localaccount_valid.ToolTip = $null + } + if ($tbJumpCloudUserName.Text -eq $hostname) { + Write-ToLog "JumpCloud Username can not be the same as the hostname" + $bMigrateProfile.IsEnabled = $false + $img_localaccount_valid.ToolTip = "JumpCloud Username can not be the same as the hostname. Please change the username." + } + }) + + $tbJumpCloudConnectKey.Add_PasswordChanged( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password)) -eq $false) { + $tbJumpCloudConnectKey.Background = "#FFC6CBCF" + $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" + $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_ckey_valid.ToolTip = "Connect Key must be 40chars & not contain spaces." + } Else { + $tbJumpCloudConnectKey.Background = "white" + $tbJumpCloudConnectKey.FontWeight = "Normal" + $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" + $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_ckey_valid.ToolTip = $null + } + }) + + $tbJumpCloudAPIKey.Add_PasswordChanged( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If (Test-IsNotEmpty $tbJumpCloudAPIKey.Password) { + $tbJumpCloudAPIKey.Background = "#FFC6CBCF" + $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 $img_apikey_valid.ToolTip = "Please enter a valid JumpCloud API Key" - $OrgSelection = "" - $lbl_orgName.Text = "" - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - Write-ToLog "MTP KEY MAY BE WRONG" - } - } - }) -$tbTempPassword.add_TextChanged( { - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - If ((!(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)) -eq $false) { - $tbTempPassword.Background = "#FFC6CBCF" - $tbTempPassword.BorderBrush = "#FFF90000" - $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_localaccount_password_valid.ToolTip = "Local Account Temp Password should not be empty or contain spaces, it should also meet local password policy req. on the system." - } Else { - $tbTempPassword.Background = "white" - $tbTempPassword.Tooltip = $null - $tbTempPassword.FontWeight = "Normal" - $tbTempPassword.BorderBrush = "#FFC6CBCF" - $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 - $img_localaccount_password_valid.ToolTip = $null - } - }) -# Change button when profile selected -$lvProfileList.Add_SelectionChanged( { - $script:SelectedUserName = ($lvProfileList.SelectedItem.username) - New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS - Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) - try { - $SelectedUserSID = ((New-Object System.Security.Principal.NTAccount($script:SelectedUserName)).Translate( [System.Security.Principal.SecurityIdentifier]).Value) - } catch { - $SelectedUserSID = $script:SelectedUserName - } - $hku = ('HKU:\' + $SelectedUserSID) - if (Test-Path -Path $hku) { - $script:bMigrateProfile.IsEnabled = $false - $script:tbJumpCloudUserName.IsEnabled = $false - $script:tbTempPassword.IsEnabled = $false - } else { - $script:tbJumpCloudUserName.IsEnabled = $true - $script:tbTempPassword.IsEnabled = $true - } - }) - -$bMigrateProfile.Add_Click( { - if ($tbJumpCloudAPIKey.Password -And $tbJumpCloudUserName.Text -And $AutobindJCUser) { - # If text field is default/ not 40 chars - if (!(Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password)) { - # Validate the the JumpCLoud Agent Conf File exists: - $keyResult = Test-JumpCloudSystemKey -WindowsDrive $(Get-WindowsDrive) - if (!$keyResult) { - # If we catch here, the system conf file does not exist. User is prompted to enter connect key; log below - Write-ToLog "The JumpCloud agent has not be registered on this system, to please specify a valid Connect Key to continue." - return + } Else { + # Get org name/ id + try { + $OrgSelection = Get-mtpOrganization -ApiKey $tbJumpCloudAPIKey.Password -inputType #-errorAction silentlycontinue + $lbl_orgName.Text = "$($OrgSelection[1])" + $Env:selectedOrgID = "$($OrgSelection[0])" + $tbJumpCloudAPIKey.Background = "white" + $tbJumpCloudAPIKey.Tooltip = $null + $tbJumpCloudAPIKey.FontWeight = "Normal" + $tbJumpCloudAPIKey.BorderBrush = "#FFC6CBCF" + $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_apikey_valid.ToolTip = $null + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + } catch { + $bMigrateProfile.IsEnabled = $false + $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_apikey_valid.ToolTip = "Please enter a valid JumpCloud API Key" + $OrgSelection = "" + $lbl_orgName.Text = "" + $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + Write-ToLog "MTP KEY MAY BE WRONG" } - } else { - Write-ToLog "ConnectKey is populated, JumpCloud agent will be installed" } - - $testResult, $JumpCloudUserId, $JCSystemUsername = Test-JumpCloudUsername -JumpCloudApiKey $tbJumpCloudAPIKey.Password -JumpCloudOrgID $Env:selectedOrgID -Username $tbJumpCloudUserName.Text -Prompt $true - if ($testResult) { - Write-ToLog "Matched $($tbJumpCloudUserName.Text) with user in the JumpCloud Console" + }) + $tbTempPassword.add_TextChanged( { + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + If ((!(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)) -eq $false) { + $tbTempPassword.Background = "#FFC6CBCF" + $tbTempPassword.BorderBrush = "#FFF90000" + $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_localaccount_password_valid.ToolTip = "Local Account Temp Password should not be empty or contain spaces, it should also meet local password policy req. on the system." + } Else { + $tbTempPassword.Background = "white" + $tbTempPassword.Tooltip = $null + $tbTempPassword.FontWeight = "Normal" + $tbTempPassword.BorderBrush = "#FFC6CBCF" + $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_localaccount_password_valid.ToolTip = $null + } + }) + + # Change button when profile selected + $lvProfileList.Add_SelectionChanged( { + $SelectedUserName = $($lvProfileList.SelectedItem.username) + Write-Host "Selected User: $($SelectedUserName)" + New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS + Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) + try { + $SelectedUserSID = ((New-Object System.Security.Principal.NTAccount($SelectedUserName)).Translate( [System.Security.Principal.SecurityIdentifier]).Value) + } catch { + $SelectedUserSID = $SelectedUserName + } + $hku = ('HKU:\' + $SelectedUserSID) + if (Test-Path -Path $hku) { + $bMigrateProfile.IsEnabled = $false + $tbJumpCloudUserName.IsEnabled = $false + $tbTempPassword.IsEnabled = $false } else { - Write-ToLog "$($tbJumpCloudUserName.Text) not found in the JumpCloud console" - return + $tbJumpCloudUserName.IsEnabled = $true + $tbTempPassword.IsEnabled = $true } - if ( -not [string]::isnullorempty($JCSystemUsername) ) { - # Regex to get the username from the domain\username string and compare it to JCSystemUsername - #Get all the local users - $registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" - $profileList = @() - foreach ($profile in $registyProfiles) { - $profileList += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath, @{ Name = "username"; Expression = { $sysUsername = Convert-Sid -sid $_.PSChildName; $sysUsername.Split('\')[1] } } - } - # If the JumpCloud found username was identified to exist locally, throw message - if ($JCSystemUsername -in $profileList.username) { - # Create a pop up that warns user then press ok to continue - Write-ToLog "JCSystemUsername $($JCSystemUsername) is the same as the another profile on this system" - $wshell = New-Object -ComObject Wscript.Shell - $message = "The JumpCloud User: $($tbJumpCloudUserName.Text) has a local account username of: $($jcsystemUserName). A local account already exists on this system with username: $($JCSystemUsername), please consider removing either the local account on this system or removing the local user account field from the JumpCloud user." - $var = $wshell.Popup("$message", 0, "JumpCloud SystemUsername and Local Computer Username Validation", 0) - # the user can not continue with migration at this stage - return + }) + $SelectedUserName = $($lvProfileList.SelectedItem.username) + + + $bMigrateProfile.Add_Click( { + if ($tbJumpCloudAPIKey.Password -And $tbJumpCloudUserName.Text -And $AutobindJCUser) { + # If text field is default/ not 40 chars + if (!(Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password)) { + # Validate the the JumpCLoud Agent Conf File exists: + $keyResult = Test-JumpCloudSystemKey -WindowsDrive $(Get-WindowsDrive) + if (!$keyResult) { + # If we catch here, the system conf file does not exist. User is prompted to enter connect key; log below + Write-ToLog "The JumpCloud agent has not be registered on this system, to please specify a valid Connect Key to continue." + return + } + } else { + Write-ToLog "ConnectKey is populated, JumpCloud agent will be installed" } - $wshell = New-Object -ComObject Wscript.Shell - $message = "The JumpCloud User: $($tbJumpCloudUserName.Text) has a local account username of: $($jcsystemUserName). After migration $($SelectedUserName) would be migrated and accessible with the local account username of: $($jcsystemUserName) Would you like to continue?" - $var = $wshell.Popup("$message", 0, "JumpCloud Local User Validation", 64 + 4) - # If user selects yes then migrate the local user profile to the JumpCloud User - if ($var -eq 6) { - Write-ToLog -Message "User selected 'Yes', continuing with migration of $($SelectedUserName) to $($jcsystemUserName)" + $testResult, $JumpCloudUserId, $JCSystemUsername = Test-JumpCloudUsername -JumpCloudApiKey $tbJumpCloudAPIKey.Password -JumpCloudOrgID $Env:selectedOrgID -Username $tbJumpCloudUserName.Text -Prompt $true + if ($testResult) { + Write-ToLog "Matched $($tbJumpCloudUserName.Text) with user in the JumpCloud Console" } else { - Write-ToLog -Message "User selected 'No', returning to form" + Write-ToLog "$($tbJumpCloudUserName.Text) not found in the JumpCloud console" return } - } else { - Write-ToLog "User $($tbJumpCloudUserName.Text) does not have a local account on this system" + if ( -not [string]::isnullorempty($JCSystemUsername) ) { + # Regex to get the username from the domain\username string and compare it to JCSystemUsername + #Get all the local users + $registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + $profileList = @() + foreach ($profile in $registyProfiles) { + $profileList += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath, @{ Name = "username"; Expression = { $sysUsername = Convert-SecurityIdentifier -sid $_.PSChildName; $sysUsername.Split('\')[1] } } + } + # If the JumpCloud found username was identified to exist locally, throw message + if ($JCSystemUsername -in $profileList.username) { + # Create a pop up that warns user then press ok to continue + Write-ToLog "JCSystemUsername $($JCSystemUsername) is the same as the another profile on this system" + $wshell = New-Object -ComObject Wscript.Shell + $message = "The JumpCloud User: $($tbJumpCloudUserName.Text) has a local account username of: $($jcsystemUserName). A local account already exists on this system with username: $($JCSystemUsername), please consider removing either the local account on this system or removing the local user account field from the JumpCloud user." + $var = $wshell.Popup("$message", 0, "JumpCloud SystemUsername and Local Computer Username Validation", 0) + # the user can not continue with migration at this stage + return + } + $wshell = New-Object -ComObject Wscript.Shell + $message = "The JumpCloud User: $($tbJumpCloudUserName.Text) has a local account username of: $($jcsystemUserName). After migration $($SelectedUserName) would be migrated and accessible with the local account username of: $($jcsystemUserName) Would you like to continue?" + $var = $wshell.Popup("$message", 0, "JumpCloud Local User Validation", 64 + 4) + # If user selects yes then migrate the local user profile to the JumpCloud User + + if ($var -eq 6) { + Write-ToLog -Message "User selected 'Yes', continuing with migration of $($SelectedUserName) to $($jcsystemUserName)" + } else { + Write-ToLog -Message "User selected 'No', returning to form" + return + } + } else { + Write-ToLog "User $($tbJumpCloudUserName.Text) does not have a local account on this system" + } } - } - # Build FormResults object - Write-ToLog "Building Form Results" - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('InstallJCAgent') -Value:($InstallJCAgent) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('AutobindJCUser') -Value:($AutobindJCUser) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('BindAsAdmin') -Value:($BindAsAdmin) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('LeaveDomain') -Value:($LeaveDomain) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('ForceReboot') -Value:($ForceReboot) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('SelectedUserName') -Value:($SelectedUserName) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudUserName') -Value:($tbJumpCloudUserName.Text) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('TempPassword') -Value:($tbTempPassword.Text) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudConnectKey') -Value:($tbJumpCloudConnectKey.Password) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudAPIKey') -Value:($tbJumpCloudAPIKey.Password) - Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudOrgID') -Value:($Env:selectedOrgID) - # Close form - $Form.Close() - }) - -$tbJumpCloudUserName.add_GotFocus( { - $tbJumpCloudUserName.Text = "" - }) - -$tbJumpCloudConnectKey.add_GotFocus( { - $tbJumpCloudConnectKey.Password = "" - }) - -$tbJumpCloudAPIKey.add_GotFocus( { - $tbJumpCloudAPIKey.Password = "" - }) - -# lbl_connectkey link - Mouse button event -$lbl_connectkey.Add_PreviewMouseDown( { [System.Diagnostics.Process]::start('https://console.jumpcloud.com/#/systems/new') }) - -# lbl_apikey link - Mouse button event -$lbl_apikey.Add_PreviewMouseDown( { [System.Diagnostics.Process]::start('https://support.jumpcloud.com/support/s/article/jumpcloud-apis1') }) - -# move window -$Form.Add_MouseLeftButtonDown( { - $Form.DragMove() - }) -$Form.Add_Closing({ - # exit and close form - $FormResults = $null - Return $FormResults - }) -# Put the list of profiles in the profile box -$Profiles | ForEach-Object { $lvProfileList.Items.Add($_) | Out-Null } -#=========================================================================== -# Shows the form & allow move -#=========================================================================== - -$Form.Showdialog() + # Build FormResults object + Write-ToLog "Building Form Results" -If ($bMigrateProfile.IsEnabled -eq $true) { - Return $FormResults + if ([System.String]::isnullorempty($SelectedUserName)) { + # TODO: I've broken the conversion for the username here, need to figure out why this no longer works. + $SelectedUserName = $($lvProfileList.SelectedItem.username) + } + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('InstallJCAgent') -Value:($InstallJCAgent) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('AutobindJCUser') -Value:($AutobindJCUser) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('BindAsAdmin') -Value:($BindAsAdmin) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('LeaveDomain') -Value:($LeaveDomain) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('ForceReboot') -Value:($ForceReboot) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('SelectedUserName') -Value:($SelectedUserName) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudUserName') -Value:($tbJumpCloudUserName.Text) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('TempPassword') -Value:($tbTempPassword.Text) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudConnectKey') -Value:($tbJumpCloudConnectKey.Password) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudAPIKey') -Value:($tbJumpCloudAPIKey.Password) + Add-Member -InputObject:($FormResults) -MemberType:('NoteProperty') -Name:('JumpCloudOrgID') -Value:($Env:selectedOrgID) + # Close form + $Form.Close() + }) + + $tbJumpCloudUserName.add_GotFocus( { + $tbJumpCloudUserName.Text = "" + }) + + $tbJumpCloudConnectKey.add_GotFocus( { + $tbJumpCloudConnectKey.Password = "" + }) + + $tbJumpCloudAPIKey.add_GotFocus( { + $tbJumpCloudAPIKey.Password = "" + }) + + # lbl_connectkey link - Mouse button event + $lbl_connectkey.Add_PreviewMouseDown( { [System.Diagnostics.Process]::start('https://console.jumpcloud.com/#/systems/new') }) + + # lbl_apikey link - Mouse button event + $lbl_apikey.Add_PreviewMouseDown( { [System.Diagnostics.Process]::start('https://support.jumpcloud.com/support/s/article/jumpcloud-apis1') }) + + # move window + $Form.Add_MouseLeftButtonDown( { + $Form.DragMove() + }) + $Form.Add_Closing({ + # exit and close form + $FormResults = $null + Return $FormResults + }) + # Put the list of profiles in the profile box + $Profiles | ForEach-Object { $lvProfileList.Items.Add($_) | Out-Null } + #=========================================================================== + # Shows the form & allow move + #=========================================================================== + + $Form.Showdialog() + + If ($bMigrateProfile.IsEnabled -eq $true) { + Return $FormResults + } } \ No newline at end of file From c3236b49eaf6b2a0183a1272e35ba979186dd6fd Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 13:45:15 -0700 Subject: [PATCH 06/73] move progress form --- .../DisplayForms}/ProgressForm.ps1 | 0 .../DisplayForms/Show-MtpSelection.ps1 | 111 ++++++++++++++++++ 2 files changed, 111 insertions(+) rename jumpcloud-ADMU/Powershell/{ => Private/DisplayForms}/ProgressForm.ps1 (100%) create mode 100644 jumpcloud-ADMU/Powershell/Private/DisplayForms/Show-MtpSelection.ps1 diff --git a/jumpcloud-ADMU/Powershell/ProgressForm.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 similarity index 100% rename from jumpcloud-ADMU/Powershell/ProgressForm.ps1 rename to jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Show-MtpSelection.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Show-MtpSelection.ps1 new file mode 100644 index 00000000..f534525c --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Show-MtpSelection.ps1 @@ -0,0 +1,111 @@ +function Show-MtpSelection { + [OutputType([object[]])] + [CmdletBinding()] + param ( + [Parameter()] + [System.Object] + $Orgs + ) + begin { + $Prompt = 'Please Select A JumpCloud MTP:' + $Title = 'MTP Organization Selection' + # define a data table to store org names/ org ids + $datatable = New-Object system.Data.DataTable + #Define Columns + $col1 = New-Object system.Data.DataColumn "Value", ([string]) + $col2 = New-Object system.Data.DataColumn "Text", ([string]) + #add columns to datatable + $datatable.columns.add($col1) + $datatable.columns.add($col2) + # Define Buttons: + $okButton = [System.Windows.Forms.Button]@{ + Location = '290,12' + Size = '60,22' + Text = 'OK' + DialogResult = [System.Windows.Forms.DialogResult]::OK + } + $cancelButton = [System.Windows.Forms.Button]@{ + Location = '290,40' + Size = '60,22' + Text = 'Cancel' + DialogResult = [System.Windows.Forms.DialogResult]::Cancel + } + # label for the form + $label = [System.Windows.Forms.Label]@{ + AutoSize = $true + Location = '10,10' + Size = '240,20' + MaximumSize = '250,0' + Text = $Prompt + } + $dynamicLabel = [System.Windows.Forms.Label]@{ + AutoSize = $true + Location = '10,30' + Size = '240,20' + MaximumSize = '250,0' + Text = '' + } + foreach ($org in $orgs) { + #Create a row + $name = New-Variable -Name "row_$($org._id)" + $name = $datarow1 = $datatable.NewRow() + #Enter data in the row + $name.Text = "$($org.DisplayName)" + $name.Value = "$($org._id)" + #Add the row to the datatable + $datatable.Rows.Add($name) + } + #create a combobox + $comboBox = [System.Windows.Forms.ComboBox]@{ + Location = '10,90' + AutoSize = $true + MaximumSize = '500,0' + # MaximumSize = '335,0' + DropDownStyle = "DropDownList" + } + $SelectBox = [System.Windows.Forms.Form]@{ + Text = $Title + Size = '369,159' + # Size = '369,159' + StartPosition = 'CenterScreen' + AcceptButton = $okButton + CancelButton = $cancelButton + FormBorderStyle = 'FixedDialog' + MinimizeBox = $false + MaximizeBox = $false + } + } + process { + #clear combo before we bind it + $combobox.Items.Clear() + + #bind combobox to datatable + $combobox.ValueMember = "Value" + $combobox.DisplayMember = "Text" + $combobox.Datasource = $datatable + + #add combobox to form + $SelectBox.Controls.Add($combobox) + + #show form + $SelectBox.Controls.AddRange(@($okButton, $cancelButton, $label, $dynamicLabel)) + $SelectBox.Topmost = $true + $SelectBox.Add_Shown({ $comboBox.Select() }) + + } + end { + $combobox.Add_SelectedIndexChanged({ + #output the selected value and text + $dynamicLabel.Text = "OrgName: $($combobox.SelectedItem['Text'])" + $dynamicLabel.Refresh(); + # write-host $combobox.SelectedItem["Value"] $combobox.SelectedItem["Text"] + }) + $result = $SelectBox.ShowDialog() + if ($result -eq [System.Windows.Forms.DialogResult]::OK) { + # return id of the org we selected + return $combobox.SelectedItem["Value"], $combobox.SelectedItem["Text"] + } else { + return $null + } + } +} \ No newline at end of file From 96c5bd395d8b40c06719da1383c07f7c7feb18b9 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 14:03:48 -0700 Subject: [PATCH 07/73] process by owner changes --- jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 b/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 index 00060fe5..80cab1c6 100644 --- a/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Close-ProcessByOwner.ps1 @@ -4,7 +4,7 @@ function Close-ProcessByOwner { # Parameter help description [Parameter(Mandatory = $true)] [System.Object] - $ProcesssList, + $ProcessList, # force close processes [Parameter()] [bool] @@ -18,7 +18,7 @@ function Close-ProcessByOwner { process { switch ($force) { $true { - foreach ($item in $ProcesssList) { + foreach ($item in $ProcessList) { Write-ToLog "Attempting to close processID: $($item.ProcessId)" $tkStatus = taskkill /t /f /PID $item.ProcessId 2>&1 $tkSuccess = if ($tkStatus -match "ERROR") { @@ -36,7 +36,7 @@ function Close-ProcessByOwner { } } $false { - foreach ($item in $ProcesssList) { + foreach ($item in $ProcessList) { $resultList.Add( [PSCustomObject]@{ ProcessName = $item.ProcessName From b2ef6ce7ee9024d972b017f431a98570d4089e8d Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 14:04:02 -0700 Subject: [PATCH 08/73] progress forms changes --- .../Private/DisplayForms/ProgressForm.ps1 | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 index 53af628a..f470009d 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 @@ -1,18 +1,3 @@ -$newJCLogoBase64 = "iVBORw0KGgoAAAANSUhEUgAAAggAAABTCAYAAAD6Kv9+AAAACXBIWXMAABcRAAAXEQHKJvM/AAAUt0lEQVR4nO2dTXKbyhbH/0m9YirfFViZU2XfFZhMmURvBSYriLKCkBVEXkHQCq7vhOlFK7hyFfOgFTx7yiRv0Acby0Lqhv4CnV9VKomNmiPoj3+f03363e/fv8EIgjC+BnABIOq4pADwWJf51pZNDMMwDOOCd+csEIIwngNYQAiCT4of3wC4B3Bfl3ml1TCGYRiGccxZCoQgjBMACYAbTUVuAGR1mWeaymMYhmEYp5yVQAjCeAFgBeDS0C12AFIWCgzDMMzYOQuBQKGEDPo8BqfYAEg49MAwDMOMlfeuDTANhRO2sCcOQPfa0r0ZhmEYZnRM2oMQhHEG4NaxGeu6zBPHNjAMwzCMEpP1IHgiDgDglmxhGIZhmNEwSYHgkThoYJHAMAzDjIrJCQQPxUEDiwSGYRhmNExKINCiQB/FQcNtEMZL10YwDMMwzCkms0iR0iQXAGaOTZHhT07XzDAMw/jMlARCAbtbGYfwACCFSPF8DWCOt8mbdgAqiC2aBYCiLvNHS/YxDMMwZ84kBAKFFn66tsMCa4iUzoVrQxiGYZhpMxWBUMFc+mQf2UCkdC5cG8IwDMNMk9ELhDPyHhzib4iUzhx6YBiGYbQyhV0MqWsDHPIJIqXztWtDGIZhmGkxaoFAA+M5hRYOcQmgCMI4cm0IwzAMMx1GLRAAJK4N8IQZgH9YJDAMwzC6GPUahCCMtwCuXNvhEU8AoinkWNgTO1teZ8EwDGOX0QqEIIwvAPzPtR0esgNwPdYBlTJNpnib8OoOYufGKL8XwzDM2PBeINA6gwgimVB7Md4F2HvQxV1d5qNL6SxxjsYDhIeERQLDMIxhvBQIQRjPIWaRC4wjdbKPfBxTnoQgjBcA/pK49Htd5qlhcxjGGyjcFklcWtVlnhk1htEOjXeJ5OVZXeaVMWP2+I+tG8lADyrDeFIm+0wKuU7FFxLJ65oQBMOcCxGAbxLXbSD6T2ZczCH3fgGRdr8yZcg+3uxiCMI4BfALLA50cTOyXQ2fJK+bjex7MQzDjBLnHgRabFiA1xOYIIF4tgzDMAyjhFMPAi1ArMDiwBQL1wYosFO4tjJlBMMwDCNwJhBIHBSY7iLEHcTpi58BfATwR13m7+oyf0f//wjgO8TKfFOMyR1fSF63s7lIh2EY5lxxEmKgsEKGaYqDDYBVXeb3XRe0dhcUANLWKtYl9D+TCOMIM6SQ27WSGLeEYRiGceZByDC9sMIDxNbC6Jg4OERd5hVt3ZtDJATSySgOciKvQASRDbKLz2PauskwDDNmrAsE2u8uu2J9LNzVZX49dPCqy/yREhx9xPGBUoULTeUYh1JEzyFCLxv68QNEqOYD7/FmGIaxx6AQA4UKmkyHgOjc53hxaVcQefTbZwOshtzTQz7rHrjqMi9ojcY9hntabqisa4h3A4j39QigeS9biPdUDbzXYChLYuraDoZhmHNHWSBQvHwBEQvuGrxe5TIIwvgJYrB7xLSOZ9YuDhrqMq9ogWGB4SLh346fv/LkBGG8g3hPKx/EAsMwDOMO6RBDEMZzypX/C8APqA1aM4gc+1+UrPOb76Zd3jSbTqAv3HCKS4h39CsI42JEOyAYhmEYzUh5ECjLoWwqSNs8QHgmGmxkYtzYOg+gLvNtEMYJ5M4p0MkNgH+CMF4DWPIBSX7R8uRFEOtMriHaQUV/ir4ClupbBBGSuqC/m3BUAeDe1ZHiFNZsvvccLzZuIb5/Y19l0IYmrNoO280hBPamdekWL+9iNEewU92KIL5T1PrVDcT27Yr+P8rvpxuTbdE1Rw9r8jTLYeMGz7oqZasBL2EmpPHBtgs+COMC7tJQ7wAsTHYCNCglEpduj51UGYSx7OljSoc+qYhkynXRVY6sfQcP22odZHbs1MuGJwCJ7K6aI0dtH2IDIRyl6oSO50dlyG4F1ipsNRwgt4PYvbVStUnh2W3qMo+ULcNzX5/geOj4GIPDkwbbbgTgH8nLpQ+5o2e2gnxbTOsyf7MGz5R9Ouj0IHiYyGgH8YCzUxdSp7UFsKJdEyvoEwprR/H5BCK844JLAEUQxkuDSngOPofjKCSiVpBvkzMAfwVhvK7LPDlS7gVE567y/G8A/BuEsbF1OA09Jyq3AKIgjAcJW0VBdoxLiEF+GYTxwYHCBfRslxieg6UJT345B69jj/FxBuAHjUeLsTybg2sQPBQHawDXfTqiuszv6zKfQ19+gVRTOUqQKPnbxb2JGYCfNEgxlqHn/hP92uQtzUIPldsMvn3F2U/q9Iww0IvZCNteuUDomW8xXBy0aQaKgr6bM+i5bCGEi86+/hZAZbJeuGTg+HgDUSdHsf38jUBoNUhfxMHnusyToYqL3NKfB9qycby6P3N474ZV3w6X6Qe5IH8OLOYbzYb30bGVNjPY4RUYZt8MwL2qfUEYr9BfkMngdKAg8VPA3K6yxnuVGirfCZrGxyv40Zef5JAH4R5+iYNMV2FU1hCRoJQhUTeqGRoN0avDZfrRcv/rIN0rewk9YZ0ZhItaKzS46Fj/dAkFzx+JAxs7rq7gIC/MQG+UKt8mJhJUQnzH+DQGb+wrgaCxw9DB2kRsk8r83vPjhT5LerM5fYlxLjG9hFe+kkJfR/7s8iXhkWoqF9AsEMjbobPMLx0elP37LmB3O/atTVc8ef9st91vUwg3UP3RGW5a+T7RehYIBjqMIexgYEbSQCtglQdaT7by+GADIDq2yLURU4Y6JJ2DVft0z6GL0o6VrQPd9jVldtI6RM42NgfsDG48xCbDULZINZc3g+eHz7U9CCYaZF9SC6s8U8XrdyaM6IFPq19T1wZMnMRAmRH9bUKARyevkCfRWJZsmbrcx6pc2phhk4fY1Zb1GUbsdWzl39CNsYmwDt4Dr7a6+MDORlIJ2kuq4kWozFgyappzHhgzJHv/30HsZGkOs+qTYXNOg9H+QLiB2C10h/5hLC11gWKz+/Y1h3Y1372PYO/0cgxwHzfv4yP9+Up2qpL0+IwqqeL1O4jv86Eu83fNHwB/QKzlUq0ntzJhHk+J0D//xQaijqwh6nGbS3h84m6TB6Fv8g8T2Ha3+bLmYqwk8EdcTol2kq8NhFet2L+IZoU/FMq9xusTPr/jQPIe6shXUDt5VZcLub1j4w4dyXdosM+gthI/wuG1RKp1eAORhGrfroJsS6G2Q8ToCbcdousYnbkzqK5kEGED1frX5FwYG6regweIXBDF/i8OtC2V52eVJsTg0wKSwuK9VFaHz00ZMXJ8qjtTouk81nWZR13Z0yjhzleFcq9aZX+sy/xgOK8u86ou8wXUZom6Y8yf6zJfdm0tpmdyDTVvQtTx80ShjA29k4N2Ac95SyK8nTF2YnhNj0o7PZpYq02P+idVroeozPIfABxrs03b6rtY3hqNQIhcGtHiyeZCQOoYZRuwL6dQ+rbQ53LEbkPf2ch01NRJq7rcv0qmbFWZ7emMb0sdhtY60EyWNx09Dcyys+snSA62PWyLFK5VRdZD8QTFGT7VP1khORtpWFK2bj9BMlMiLZZ3mfzuJO/pZfkSXnCxQr+SvdCTiu2DDftErg2YKKnCtZnCtTvZVL8k2G0v0H1SybWvuJ7oUF8Xyd4Limcp0POTHQSMtG1Fz4TyWRHN5xSuHZXXUbHfVz0ozOtwy3v4NyO1jYooiUwZIQMtJvVxzcTctQET5EHxUBaVa1UTL1WK1w8lM/mZAwOmygAgfZ8WXc+7WXj5FSLcY2rgjBSu7ZWUi5K4yS6a9XGScwyVMTJTKdiDFPpH+Q949qdCArdbdXxV3ucuMk1QGCzbh4ycx+hj3xDv41zyuqeeqdbvIfqObfPHck4V2fY5NMS7hdwEZmz9hbSg6XnSYgHDi1T70nma4xkxV7j2KgjjucPzGBJH9z3F2GYEY8CnfBe2qVQ/UJf5NgjjvveTjS/3GjzJZR/1+awmZNvnUNFSQE4g+OgFPYasoJFekLqHL8nv3nDwNEeHzEdwz9SADScht+jYGhbDKGNBgEc9P1dptIGZHpMT9b4JhEsH6ThVB91bR4sVUwf3lKVybQDDWKBybQDD2OQ9/HNvWIuzD0hvmum04xSeHaJ1iMq1AQzDMIxe3sM/t0hi8V59BcIVHQlrHPJWpDbuNQDf6hDDmIDX2jBnxfueqy5NcmPjlEANR3d+MX2eN4mDAv7kqeiicG0AMx0shBmLnp8b2+p72/DzmRjNGoS+h7OYIvX8HjuIHPGVFku6eYSws+/qWBtYzX7JnAXKM3VL64J63yMI48cgjIsgjFMHx6TLts+hYUzZ52N7vLHl+el7H2+FVSMQfNsXfUNxdyNQA+3jPVgD+G9d5nPKEV9oNWwPytm9qsv8GsAHiNzdvhw73eBb3TGFrzkopkifjnbIIGA0TXArW+0NgG8A/gnC+DcJhlUQxgvDXhPpEOBAoSUrMIaGJFWf1VCBICuwZj3Tzkc9PmMFXwUCAPwwMSugMlW+7xPEwPyhLvOEMoZZh8RCWpf5HOKoVV+Ego91R4a57IXU6HWeM8Acp8/kQEXA7Xf4lcJn+9iWdPz8BsAXAH8B+F8QxlmPsmUoFK5N+txAMdw61OMoPS6Q8Boq7lUETZ97JT0+Y4X3wPO+4z5nmJvGhBchUbh2DeCaBubKgC29qMs8awkF2fSmJti5EkwaUGnILrNnniOXKgMOCTjpw4gOnDWgMmAtVGaJNEAlCuVrR9HTmfT0Zqj01UP7jBuFd7DE8DVcKvVDacwiT7m3a8zaeRBSV0bssYEY+P6QPXJUBQoNXAD4L7pzYO8gcqMfOu/dG+i0uznc5fJOHd1XB7MgjNNTF1ED9jIN6sRZKXgQVQacQ529yudnAO4VBtEM8gNAoWCHKrJ9xAyK27ipHUmfdnhkzZLKZOekaCeR+U2hzIOQoJS17VLWE0T1+0dfu2zwnGq5LvMqCOM7CJeXC9YArM3UaeZ7T0p0CaHyZxANKel5opl1yM4FNYYV7KnRjcxxvJ7zLQjj5tjVV1C9WOFFHGzgdy6KKdE86yII42VXPaN3lEEt/FPs/4D6vgeFcq7ItkVXf0UCIoOauHxjm0buIW/LJxrklqf6QRIHKoNwduR3smc5AEdspGef4mUs09F2C8g/v1tK+935/GjikdJ/dwAuB9rXlBvh+JqGDC2PVrvvo/bU/K6qyzzbP4shhXC9ajFWkg3EgFxZvOczdN8l5TVYyB6D6xt1mWdBGG9hZ1vkE/yNm6l09IAQCUuI57aFWAAVHShjBRYItmie9QzATxqEMoi1AhXE+7lGP89O0fHzDGqzuSsAv4Iw/hsvdQcQHr0Ioh9VaYcPJvtA6h9UJhC3EBOPDEJcbJvBjgaha4iJlepYobN/bWxs+r0Lsmu/nS4B/DvwXgXU6ltj2z1eDumak337Y+wK+jwJEY4LtqL9+yCMt60w8aL1uw2A1wKhLvNHyi5YwM4g0zk7sA01zlGKgwY6sCaC+fe39Dj0UkF9QeEMovF3dQBrcDIoa9Rlfh+E8RNe6vAlNLiKIdzbRcfvMogJkmq7OVZvVLDR96RQG4hmELPwLwAw4DCshvWJfqOAughvdod0fe5h4EFeDfdQH8RnEELh2I65B2jMZkwegRQAgjAuIJ7Lx6beH1i7scBLiG0JMS4/t4E3ZzFQfMjYFkNiByDyRRxMCXp/icFbfPf8vRUGyswMlMkcx8SAmXX9gmbHpvu9LnY22hR5R13lVHnC6eerbaBsoaUekbAxkb/B9qR0Tn8332URhPEFrYe4xItYuAE6DmuiyvrZkIEPEDsDTFQGBs/rK+4MFH13KF7vGbp3VWw8zDZ6Dqygf4fO0c6Y+j0XC35tCpMEbnY+nVzXRf2WTtt0C69UY1mAJWHYQQUhEmYQXoSEfl60L+o8zZEM/wi9L+wJIs7P7lrzpND77j7XZe5qhiWNZqXv81qLSUN9hM7Z1Z1kWCyB3Vn22uZWYUse4n3uFL6jzmeRaCyr2S6q04vgsj+t8PKsI7wsbHwl0o4e90wPZA59D8XrbYNTQmMH+wDgT8/DCvukmsrxea3F5CFvlY6+ZwfJOkHtJoIdkfAAB4OEYQ/xPneKE4sUeiY2d4Y8fwn02GdVGLaIWv9uC4QrAH9T/d8CYhvmUYEAiAZTl3kEkTdgSPa+zYiT6oyVbMBnnwB8rct8dOEg6hiGhljuRiaKpsoCwwZrZa9lSySYDDesIdZhOfGmUt3+E+Yysj6hh9ex2VU28N5rU95Osi8ZWMyDiRw/qtB3ecDLjor98fnipEBoFXZP2fuOJRg6RtrjM8wAWhVAhQeI2cV8rFs+AZEQC/2zg44inHIODJzRN4uhlQUuTYwWGD4x2qcR3s5zrdBzuYZIJa+TJgNt1ufDAz0cd6YHX5ro9s1iu4YfZy9U9HdbFLyZwO/nQThJK8FQs1/8Gqe/8CMv9HJGgdNb9AqIClM4dKlf6C6wLvOEtvrI7v/eQIQVDg0oj9Dj7pYto1IsV8U+1YHJqQeJBtJryoewhNy7XEMi0Y/EvZv+LoHwZvTd0riDqIdZD5sqyL3bXkIIQEo5EpYQs+M+eXCeIAYYLcnuKG9DEyaVsWcHEcIuOn6vtW2QfQWEl1Zma+YO4tlkHfc00XYzvPTtDcXe3yv692OrXj5/7t3v378V7scwZmjt2T3FhkJequUnEEJ23rrPA0SDKwDcjy2UMiZUMu7VZf7uSDkXEAP1AkJUtt9lhZd3WfU29gh7E6NrsuECr3NvNJ39lmwyZo8JaK98hJfvCLxum027qfAysSgM2tO87znZM4MYcCuIZ5y5bLu0RXCBl/7lEkIwtd//KMPrLBAY51CH9EvyctVFT4wH6BIIDMPYQznEwDBDoRlB486aQ219SqXZHIZhGOYALBAYq5CL9q8BRRSaTGEYhmGOIL2LgWE0cX36kk52vE6AYRjGDiwQmDGRuTaAYRjmXGCBwIyFJ4z8tE2GYZgxwQKBGQup68QyDMMw5wQLBMY28x6fWY85qyPDMMwYYYHA2GaueP13H/KWMwzDnBu8zZHxlWNpjxmGYRjDsEBgbFNBDP5zvORYb9KmVhDpSUeVmpaRIgPnsGCYUfF/ROMEAmQdLQsAAAAASUVORK5CYII=" -function DecodeBase64Image { - param ( - [Parameter(Mandatory = $true)] - [String]$ImageBase64 - ) - # Parameter help description - $ObjBitmapImage = New-Object System.Windows.Media.Imaging.BitmapImage #Provides a specialized BitmapSource that is optimized for loading images using Extensible Application Markup Language (XAML). - $ObjBitmapImage.BeginInit() #Signals the start of the BitmapImage initialization. - $ObjBitmapImage.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($ImageBase64) #Creates a stream whose backing store is memory. - $ObjBitmapImage.EndInit() #Signals the end of the BitmapImage initialization. - $ObjBitmapImage.Freeze() #Makes the current object unmodifiable and sets its IsFrozen property to true. - $ObjBitmapImage -} - function New-ProgressForm { # Synchash the values [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null @@ -24,7 +9,7 @@ function New-ProgressForm { $syncHash.StatusInput = '' $syncHash.LogText = @() $synchash.logLevel = '' - $syncHash.base64JCLogo = DecodeBase64Image -ImageBase64 $newJCLogoBase64 + $syncHash.base64JCLogo = DecodeBase64Image -ImageBase64 $JCLogoBase64 $synchash.closeWindow = $false # Migration Details From 61f2c05b7fc4b62194de93d2d7c49be63ca0ed4a Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 14:04:15 -0700 Subject: [PATCH 09/73] Assets file for forms --- jumpcloud-ADMU/Powershell/Private/DisplayAssets/Images.ps1 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 jumpcloud-ADMU/Powershell/Private/DisplayAssets/Images.ps1 diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayAssets/Images.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayAssets/Images.ps1 new file mode 100644 index 00000000..df928251 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/DisplayAssets/Images.ps1 @@ -0,0 +1,4 @@ +$JCLogoBase64 = "iVBORw0KGgoAAAANSUhEUgAAAggAAABTCAYAAAD6Kv9+AAAACXBIWXMAABcRAAAXEQHKJvM/AAAUt0lEQVR4nO2dTXKbyhbH/0m9YirfFViZU2XfFZhMmURvBSYriLKCkBVEXkHQCq7vhOlFK7hyFfOgFTx7yiRv0Acby0Lqhv4CnV9VKomNmiPoj3+f03363e/fv8EIgjC+BnABIOq4pADwWJf51pZNDMMwDOOCd+csEIIwngNYQAiCT4of3wC4B3Bfl3ml1TCGYRiGccxZCoQgjBMACYAbTUVuAGR1mWeaymMYhmEYp5yVQAjCeAFgBeDS0C12AFIWCgzDMMzYOQuBQKGEDPo8BqfYAEg49MAwDMOMlfeuDTANhRO2sCcOQPfa0r0ZhmEYZnRM2oMQhHEG4NaxGeu6zBPHNjAMwzCMEpP1IHgiDgDglmxhGIZhmNEwSYHgkThoYJHAMAzDjIrJCQQPxUEDiwSGYRhmNExKINCiQB/FQcNtEMZL10YwDMMwzCkms0iR0iQXAGaOTZHhT07XzDAMw/jMlARCAbtbGYfwACCFSPF8DWCOt8mbdgAqiC2aBYCiLvNHS/YxDMMwZ84kBAKFFn66tsMCa4iUzoVrQxiGYZhpMxWBUMFc+mQf2UCkdC5cG8IwDMNMk9ELhDPyHhzib4iUzhx6YBiGYbQyhV0MqWsDHPIJIqXztWtDGIZhmGkxaoFAA+M5hRYOcQmgCMI4cm0IwzAMMx1GLRAAJK4N8IQZgH9YJDAMwzC6GPUahCCMtwCuXNvhEU8AoinkWNgTO1teZ8EwDGOX0QqEIIwvAPzPtR0esgNwPdYBlTJNpnib8OoOYufGKL8XwzDM2PBeINA6gwgimVB7Md4F2HvQxV1d5qNL6SxxjsYDhIeERQLDMIxhvBQIQRjPIWaRC4wjdbKPfBxTnoQgjBcA/pK49Htd5qlhcxjGGyjcFklcWtVlnhk1htEOjXeJ5OVZXeaVMWP2+I+tG8lADyrDeFIm+0wKuU7FFxLJ65oQBMOcCxGAbxLXbSD6T2ZczCH3fgGRdr8yZcg+3uxiCMI4BfALLA50cTOyXQ2fJK+bjex7MQzDjBLnHgRabFiA1xOYIIF4tgzDMAyjhFMPAi1ArMDiwBQL1wYosFO4tjJlBMMwDCNwJhBIHBSY7iLEHcTpi58BfATwR13m7+oyf0f//wjgO8TKfFOMyR1fSF63s7lIh2EY5lxxEmKgsEKGaYqDDYBVXeb3XRe0dhcUANLWKtYl9D+TCOMIM6SQ27WSGLeEYRiGceZByDC9sMIDxNbC6Jg4OERd5hVt3ZtDJATSySgOciKvQASRDbKLz2PauskwDDNmrAsE2u8uu2J9LNzVZX49dPCqy/yREhx9xPGBUoULTeUYh1JEzyFCLxv68QNEqOYD7/FmGIaxx6AQA4UKmkyHgOjc53hxaVcQefTbZwOshtzTQz7rHrjqMi9ojcY9hntabqisa4h3A4j39QigeS9biPdUDbzXYChLYuraDoZhmHNHWSBQvHwBEQvuGrxe5TIIwvgJYrB7xLSOZ9YuDhrqMq9ogWGB4SLh346fv/LkBGG8g3hPKx/EAsMwDOMO6RBDEMZzypX/C8APqA1aM4gc+1+UrPOb76Zd3jSbTqAv3HCKS4h39CsI42JEOyAYhmEYzUh5ECjLoWwqSNs8QHgmGmxkYtzYOg+gLvNtEMYJ5M4p0MkNgH+CMF4DWPIBSX7R8uRFEOtMriHaQUV/ir4ClupbBBGSuqC/m3BUAeDe1ZHiFNZsvvccLzZuIb5/Y19l0IYmrNoO280hBPamdekWL+9iNEewU92KIL5T1PrVDcT27Yr+P8rvpxuTbdE1Rw9r8jTLYeMGz7oqZasBL2EmpPHBtgs+COMC7tJQ7wAsTHYCNCglEpduj51UGYSx7OljSoc+qYhkynXRVY6sfQcP22odZHbs1MuGJwCJ7K6aI0dtH2IDIRyl6oSO50dlyG4F1ipsNRwgt4PYvbVStUnh2W3qMo+ULcNzX5/geOj4GIPDkwbbbgTgH8nLpQ+5o2e2gnxbTOsyf7MGz5R9Ouj0IHiYyGgH8YCzUxdSp7UFsKJdEyvoEwprR/H5BCK844JLAEUQxkuDSngOPofjKCSiVpBvkzMAfwVhvK7LPDlS7gVE567y/G8A/BuEsbF1OA09Jyq3AKIgjAcJW0VBdoxLiEF+GYTxwYHCBfRslxieg6UJT345B69jj/FxBuAHjUeLsTybg2sQPBQHawDXfTqiuszv6zKfQ19+gVRTOUqQKPnbxb2JGYCfNEgxlqHn/hP92uQtzUIPldsMvn3F2U/q9Iww0IvZCNteuUDomW8xXBy0aQaKgr6bM+i5bCGEi86+/hZAZbJeuGTg+HgDUSdHsf38jUBoNUhfxMHnusyToYqL3NKfB9qycby6P3N474ZV3w6X6Qe5IH8OLOYbzYb30bGVNjPY4RUYZt8MwL2qfUEYr9BfkMngdKAg8VPA3K6yxnuVGirfCZrGxyv40Zef5JAH4R5+iYNMV2FU1hCRoJQhUTeqGRoN0avDZfrRcv/rIN0rewk9YZ0ZhItaKzS46Fj/dAkFzx+JAxs7rq7gIC/MQG+UKt8mJhJUQnzH+DQGb+wrgaCxw9DB2kRsk8r83vPjhT5LerM5fYlxLjG9hFe+kkJfR/7s8iXhkWoqF9AsEMjbobPMLx0elP37LmB3O/atTVc8ef9st91vUwg3UP3RGW5a+T7RehYIBjqMIexgYEbSQCtglQdaT7by+GADIDq2yLURU4Y6JJ2DVft0z6GL0o6VrQPd9jVldtI6RM42NgfsDG48xCbDULZINZc3g+eHz7U9CCYaZF9SC6s8U8XrdyaM6IFPq19T1wZMnMRAmRH9bUKARyevkCfRWJZsmbrcx6pc2phhk4fY1Zb1GUbsdWzl39CNsYmwDt4Dr7a6+MDORlIJ2kuq4kWozFgyappzHhgzJHv/30HsZGkOs+qTYXNOg9H+QLiB2C10h/5hLC11gWKz+/Y1h3Y1372PYO/0cgxwHzfv4yP9+Up2qpL0+IwqqeL1O4jv86Eu83fNHwB/QKzlUq0ntzJhHk+J0D//xQaijqwh6nGbS3h84m6TB6Fv8g8T2Ha3+bLmYqwk8EdcTol2kq8NhFet2L+IZoU/FMq9xusTPr/jQPIe6shXUDt5VZcLub1j4w4dyXdosM+gthI/wuG1RKp1eAORhGrfroJsS6G2Q8ToCbcdousYnbkzqK5kEGED1frX5FwYG6regweIXBDF/i8OtC2V52eVJsTg0wKSwuK9VFaHz00ZMXJ8qjtTouk81nWZR13Z0yjhzleFcq9aZX+sy/xgOK8u86ou8wXUZom6Y8yf6zJfdm0tpmdyDTVvQtTx80ShjA29k4N2Ac95SyK8nTF2YnhNj0o7PZpYq02P+idVroeozPIfABxrs03b6rtY3hqNQIhcGtHiyeZCQOoYZRuwL6dQ+rbQ53LEbkPf2ch01NRJq7rcv0qmbFWZ7emMb0sdhtY60EyWNx09Dcyys+snSA62PWyLFK5VRdZD8QTFGT7VP1khORtpWFK2bj9BMlMiLZZ3mfzuJO/pZfkSXnCxQr+SvdCTiu2DDftErg2YKKnCtZnCtTvZVL8k2G0v0H1SybWvuJ7oUF8Xyd4Limcp0POTHQSMtG1Fz4TyWRHN5xSuHZXXUbHfVz0ozOtwy3v4NyO1jYooiUwZIQMtJvVxzcTctQET5EHxUBaVa1UTL1WK1w8lM/mZAwOmygAgfZ8WXc+7WXj5FSLcY2rgjBSu7ZWUi5K4yS6a9XGScwyVMTJTKdiDFPpH+Q949qdCArdbdXxV3ucuMk1QGCzbh4ycx+hj3xDv41zyuqeeqdbvIfqObfPHck4V2fY5NMS7hdwEZmz9hbSg6XnSYgHDi1T70nma4xkxV7j2KgjjucPzGBJH9z3F2GYEY8CnfBe2qVQ/UJf5NgjjvveTjS/3GjzJZR/1+awmZNvnUNFSQE4g+OgFPYasoJFekLqHL8nv3nDwNEeHzEdwz9SADScht+jYGhbDKGNBgEc9P1dptIGZHpMT9b4JhEsH6ThVB91bR4sVUwf3lKVybQDDWKBybQDD2OQ9/HNvWIuzD0hvmum04xSeHaJ1iMq1AQzDMIxe3sM/t0hi8V59BcIVHQlrHPJWpDbuNQDf6hDDmIDX2jBnxfueqy5NcmPjlEANR3d+MX2eN4mDAv7kqeiicG0AMx0shBmLnp8b2+p72/DzmRjNGoS+h7OYIvX8HjuIHPGVFku6eYSws+/qWBtYzX7JnAXKM3VL64J63yMI48cgjIsgjFMHx6TLts+hYUzZ52N7vLHl+el7H2+FVSMQfNsXfUNxdyNQA+3jPVgD+G9d5nPKEV9oNWwPytm9qsv8GsAHiNzdvhw73eBb3TGFrzkopkifjnbIIGA0TXArW+0NgG8A/gnC+DcJhlUQxgvDXhPpEOBAoSUrMIaGJFWf1VCBICuwZj3Tzkc9PmMFXwUCAPwwMSugMlW+7xPEwPyhLvOEMoZZh8RCWpf5HOKoVV+Ego91R4a57IXU6HWeM8Acp8/kQEXA7Xf4lcJn+9iWdPz8BsAXAH8B+F8QxlmPsmUoFK5N+txAMdw61OMoPS6Q8Boq7lUETZ97JT0+Y4X3wPO+4z5nmJvGhBchUbh2DeCaBubKgC29qMs8awkF2fSmJti5EkwaUGnILrNnniOXKgMOCTjpw4gOnDWgMmAtVGaJNEAlCuVrR9HTmfT0Zqj01UP7jBuFd7DE8DVcKvVDacwiT7m3a8zaeRBSV0bssYEY+P6QPXJUBQoNXAD4L7pzYO8gcqMfOu/dG+i0uznc5fJOHd1XB7MgjNNTF1ED9jIN6sRZKXgQVQacQ529yudnAO4VBtEM8gNAoWCHKrJ9xAyK27ipHUmfdnhkzZLKZOekaCeR+U2hzIOQoJS17VLWE0T1+0dfu2zwnGq5LvMqCOM7CJeXC9YArM3UaeZ7T0p0CaHyZxANKel5opl1yM4FNYYV7KnRjcxxvJ7zLQjj5tjVV1C9WOFFHGzgdy6KKdE86yII42VXPaN3lEEt/FPs/4D6vgeFcq7ItkVXf0UCIoOauHxjm0buIW/LJxrklqf6QRIHKoNwduR3smc5AEdspGef4mUs09F2C8g/v1tK+935/GjikdJ/dwAuB9rXlBvh+JqGDC2PVrvvo/bU/K6qyzzbP4shhXC9ajFWkg3EgFxZvOczdN8l5TVYyB6D6xt1mWdBGG9hZ1vkE/yNm6l09IAQCUuI57aFWAAVHShjBRYItmie9QzATxqEMoi1AhXE+7lGP89O0fHzDGqzuSsAv4Iw/hsvdQcQHr0Ioh9VaYcPJvtA6h9UJhC3EBOPDEJcbJvBjgaha4iJlepYobN/bWxs+r0Lsmu/nS4B/DvwXgXU6ltj2z1eDumak337Y+wK+jwJEY4LtqL9+yCMt60w8aL1uw2A1wKhLvNHyi5YwM4g0zk7sA01zlGKgwY6sCaC+fe39Dj0UkF9QeEMovF3dQBrcDIoa9Rlfh+E8RNe6vAlNLiKIdzbRcfvMogJkmq7OVZvVLDR96RQG4hmELPwLwAw4DCshvWJfqOAughvdod0fe5h4EFeDfdQH8RnEELh2I65B2jMZkwegRQAgjAuIJ7Lx6beH1i7scBLiG0JMS4/t4E3ZzFQfMjYFkNiByDyRRxMCXp/icFbfPf8vRUGyswMlMkcx8SAmXX9gmbHpvu9LnY22hR5R13lVHnC6eerbaBsoaUekbAxkb/B9qR0Tn8332URhPEFrYe4xItYuAE6DmuiyvrZkIEPEDsDTFQGBs/rK+4MFH13KF7vGbp3VWw8zDZ6Dqygf4fO0c6Y+j0XC35tCpMEbnY+nVzXRf2WTtt0C69UY1mAJWHYQQUhEmYQXoSEfl60L+o8zZEM/wi9L+wJIs7P7lrzpND77j7XZe5qhiWNZqXv81qLSUN9hM7Z1Z1kWCyB3Vn22uZWYUse4n3uFL6jzmeRaCyr2S6q04vgsj+t8PKsI7wsbHwl0o4e90wPZA59D8XrbYNTQmMH+wDgT8/DCvukmsrxea3F5CFvlY6+ZwfJOkHtJoIdkfAAB4OEYQ/xPneKE4sUeiY2d4Y8fwn02GdVGLaIWv9uC4QrAH9T/d8CYhvmUYEAiAZTl3kEkTdgSPa+zYiT6oyVbMBnnwB8rct8dOEg6hiGhljuRiaKpsoCwwZrZa9lSySYDDesIdZhOfGmUt3+E+Yysj6hh9ex2VU28N5rU95Osi8ZWMyDiRw/qtB3ecDLjor98fnipEBoFXZP2fuOJRg6RtrjM8wAWhVAhQeI2cV8rFs+AZEQC/2zg44inHIODJzRN4uhlQUuTYwWGD4x2qcR3s5zrdBzuYZIJa+TJgNt1ufDAz0cd6YHX5ro9s1iu4YfZy9U9HdbFLyZwO/nQThJK8FQs1/8Gqe/8CMv9HJGgdNb9AqIClM4dKlf6C6wLvOEtvrI7v/eQIQVDg0oj9Dj7pYto1IsV8U+1YHJqQeJBtJryoewhNy7XEMi0Y/EvZv+LoHwZvTd0riDqIdZD5sqyL3bXkIIQEo5EpYQs+M+eXCeIAYYLcnuKG9DEyaVsWcHEcIuOn6vtW2QfQWEl1Zma+YO4tlkHfc00XYzvPTtDcXe3yv692OrXj5/7t3v378V7scwZmjt2T3FhkJequUnEEJ23rrPA0SDKwDcjy2UMiZUMu7VZf7uSDkXEAP1AkJUtt9lhZd3WfU29gh7E6NrsuECr3NvNJ39lmwyZo8JaK98hJfvCLxum027qfAysSgM2tO87znZM4MYcCuIZ5y5bLu0RXCBl/7lEkIwtd//KMPrLBAY51CH9EvyctVFT4wH6BIIDMPYQznEwDBDoRlB486aQ219SqXZHIZhGOYALBAYq5CL9q8BRRSaTGEYhmGOIL2LgWE0cX36kk52vE6AYRjGDiwQmDGRuTaAYRjmXGCBwIyFJ4z8tE2GYZgxwQKBGQup68QyDMMw5wQLBMY28x6fWY85qyPDMMwYYYHA2GaueP13H/KWMwzDnBu8zZHxlWNpjxmGYRjDsEBgbFNBDP5zvORYb9KmVhDpSUeVmpaRIgPnsGCYUfF/ROMEAmQdLQsAAAAASUVORK5CYII=" +$ErrorBase64 = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAFiSURBVHgBpVPNSsNAEJ4ZzwWv/lKfoPUN2oOl3lYKelAoPpkIelBQ4smih8YnMI8QqlQbL0JbT2bH2di0MXFD2n6w7OzMzscwMx/CP3hpqhoBtcWsySlHTmYPEL1Q67vtB8dJ52Dy8dZUZQY8k1ODfPgEur7WcfwM0eueqiJRVzyrUAQMn6x1ffPR8aZEphLN9JwmKR0fQunkKLKHF1cwvLzOkBHqXVMZmbeGOSpJQnJMK4xJvUZLQdzQBWD6GQ2HSCvbpzAIZvbgw0pGsNImZKxAAYTjcU6UZV0Qqrbw9/usCs4lgjLlRcMgQTTKJQJD5EMB6NGXPShbTwz8ZIuHg0SzE43PQKSDE111YRkwqWiz+82Drk1f6/c30d3fb9lo/I3O7U7UbAQ+NesOc1ciEhHx/nJMsKxop+M3DiNAKDBFGZBr/sYkfypKotdQiggVMlRkIvHC+nJcDfp8q+O46Zwfa3qRu77hWMMAAAAASUVORK5CYII=" +$ActiveBase64 = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGKSURBVHgBlZRPTsJQEMbfTBvccgTcKYFQlxJM2hvUEygnID2CJ6jKAZQTgCcoorK1Cf5hZ4/A1oQ3Y98j1FKLbWfTvnn9fp3ON68gcqL15NtsGhfIbLOAxibLITOECPJh0fUmWQ2kF0eB3zBreCcAbPFPsBCR/JbO0vGiP6Bm4FtYM4I4UxflYgVC9rfVwW8lxmsFSAIjks5HzwtRrYyDSpWko46Avq6oPRu6bPK4jIoFR0x0HotVH61kQ0oHCcktC5FA+jOIqb+zpxwGwE6eKA+yPPUi1U9AY5wR2CiArXQOiEfv3cGhuuZBVD9jhxo7mniN2WqkoGt1XfQGl4L5qgiSwNrz2y/e3Uws3SaKIMwcIgl+zOTriEbQfPatMhAdqI8O3edsadjxfOgWQlRFxBM92a2Xm6DofO2FxGYoc3Sz1xjPBYuVqBrMK2WGutUg5QqxdCrBNpC+0iYgFcqlNcqT7DDugUzj6XY+U/8lyHuuPfNdMtEFFp3tmYL4BQQwhbUcvZ1506zmB49h1CYDMPPcAAAAAElFTkSuQmCC" +$BlueBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAElSURBVHgBbVE7TsNAEH2z+dQ+wnKCmIY6nICCIEIXdymIYk4AnCBBEETpVBQWSjgBpEM0+Ag+ghGiQIl3GH/BcUaa1erNe292ZglFuAsLGzMGmwGItCCRZACGh1lvXtAoPYcLjYZ5AbEuDZg8wHRTMfEUtycXCax2kolCzI4dud3kYhcjf5J1GD1NwOyiHq+StqT1B/GhEnK3yjNzGLOPzdqpkhM+bJW7FBGBFMPEEZpNXW+qOgrZNoqwxEXj4SwUu0GNT/yZCIKttl5e7WyZJbUPEfB1BWx9PaebA63wfwbmEPF61cC7H+KgtyeEbBbT/oHisdz61ecYB/f9NyqBc/9K0EvUQ54VO7g7Xaa6Smn4qNFsHclH2cmAst4A7e8lpk45yy8GxWbP/ZW8WwAAAABJRU5ErkJggg==" \ No newline at end of file From 51fd7842a0c4a2ca98728e5a03bfaed4e142026c Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 14:04:31 -0700 Subject: [PATCH 10/73] Get-ImageFromB64 --- .../Powershell/Private/Get-ImageFromB64.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 diff --git a/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 new file mode 100644 index 00000000..d34873e1 --- /dev/null +++ b/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 @@ -0,0 +1,13 @@ +function DecodeBase64Image { + param ( + [Parameter(Mandatory = $true)] + [String]$ImageBase64 + ) + # Parameter help description + $ObjBitmapImage = New-Object System.Windows.Media.Imaging.BitmapImage #Provides a specialized BitmapSource that is optimized for loading images using Extensible Application Markup Language (XAML). + $ObjBitmapImage.BeginInit() #Signals the start of the BitmapImage initialization. + $ObjBitmapImage.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($ImageBase64) #Creates a stream whose backing store is memory. + $ObjBitmapImage.EndInit() #Signals the end of the BitmapImage initialization. + $ObjBitmapImage.Freeze() #Makes the current object unmodifiable and sets its IsFrozen property to true. + $ObjBitmapImage +} \ No newline at end of file From e383850519d8ae28db8f02acee5ce6d15dfa438a Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 14:06:20 -0700 Subject: [PATCH 11/73] Function Names --- .../Powershell/Private/DisplayForms/Form.ps1 | 38 +++++++++---------- .../Private/DisplayForms/ProgressForm.ps1 | 2 +- .../Powershell/Private/Get-ImageFromB64.ps1 | 2 +- .../Powershell/Public/Start-Migration.ps1 | 4 +- .../Powershell/Tests/Functions.Tests.ps1 | 8 ++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 index da366e77..36ae09a6 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 @@ -237,15 +237,15 @@ Function Show-SelectionForm { $xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object { New-Variable -Name $_.Name -Value $Form.FindName($_.Name) -Force } - $JCLogoImg.Source = DecodeBase64Image -ImageBase64 $JCLogoBase64 - $img_ckey_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 - $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_apikey_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_localaccount_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 - $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 - $img_localaccount_password_info.Source = DecodeBase64Image -ImageBase64 $BlueBase64 - $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $JCLogoImg.Source = Get-ImageFromB64 -ImageBase64 $JCLogoBase64 + $img_ckey_info.Source = Get-ImageFromB64 -ImageBase64 $BlueBase64 + $img_ckey_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 + $img_apikey_info.Source = Get-ImageFromB64 -ImageBase64 $BlueBase64 + $img_apikey_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 + $img_localaccount_info.Source = Get-ImageFromB64 -ImageBase64 $BlueBase64 + $img_localaccount_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 + $img_localaccount_password_info.Source = Get-ImageFromB64 -ImageBase64 $BlueBase64 + $img_localaccount_password_valid.Source = Get-ImageFromB64 -ImageBase64 $ActiveBase64 # Define misc static variables $WmiComputerSystem = Get-WmiObject -Class:('Win32_ComputerSystem') @@ -544,13 +544,13 @@ Function Show-SelectionForm { If ((Test-IsNotEmpty $tbJumpCloudUserName.Text) -or (!(Test-HasNoSpace $tbJumpCloudUserName.Text)) -or (Test-Localusername $tbJumpCloudUserName.Text) -or (($tbJumpCloudUserName.Text).Length -gt 20) -or $tbJumpCloudUserName.Text -eq $hostname) { $tbJumpCloudUserName.Background = "#FFC6CBCF" $tbJumpCloudUserName.BorderBrush = "#FFF90000" - $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_localaccount_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 $img_localaccount_valid.ToolTip = "Local account username can't be empty, contain spaces, already exist on the local system or match the local computer name. Username must only be 20 characters long" } Else { $tbJumpCloudUserName.Background = "white" $tbJumpCloudUserName.FontWeight = "Normal" $tbJumpCloudUserName.BorderBrush = "#FFC6CBCF" - $img_localaccount_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_localaccount_valid.Source = Get-ImageFromB64 -ImageBase64 $ActiveBase64 $img_localaccount_valid.ToolTip = $null } if ($tbJumpCloudUserName.Text -eq $hostname) { @@ -565,13 +565,13 @@ Function Show-SelectionForm { If (((Test-CharLen -len 40 -testString $tbJumpCloudConnectKey.Password) -and (Test-HasNoSpace $tbJumpCloudConnectKey.Password)) -eq $false) { $tbJumpCloudConnectKey.Background = "#FFC6CBCF" $tbJumpCloudConnectKey.BorderBrush = "#FFF90000" - $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_ckey_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 $img_ckey_valid.ToolTip = "Connect Key must be 40chars & not contain spaces." } Else { $tbJumpCloudConnectKey.Background = "white" $tbJumpCloudConnectKey.FontWeight = "Normal" $tbJumpCloudConnectKey.BorderBrush = "#FFC6CBCF" - $img_ckey_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_ckey_valid.Source = Get-ImageFromB64 -ImageBase64 $ActiveBase64 $img_ckey_valid.ToolTip = $null } }) @@ -581,7 +581,7 @@ Function Show-SelectionForm { If (Test-IsNotEmpty $tbJumpCloudAPIKey.Password) { $tbJumpCloudAPIKey.Background = "#FFC6CBCF" $tbJumpCloudAPIKey.BorderBrush = "#FFF90000" - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_apikey_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 $img_apikey_valid.ToolTip = "Please enter a valid JumpCloud API Key" } Else { @@ -594,16 +594,16 @@ Function Show-SelectionForm { $tbJumpCloudAPIKey.Tooltip = $null $tbJumpCloudAPIKey.FontWeight = "Normal" $tbJumpCloudAPIKey.BorderBrush = "#FFC6CBCF" - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_apikey_valid.Source = Get-ImageFromB64 -ImageBase64 $ActiveBase64 $img_apikey_valid.ToolTip = $null Test-Button -tbJumpCloudUserName:($tbJumpCloudUserName) -tbJumpCloudConnectKey:($tbJumpCloudConnectKey) -tbTempPassword:($tbTempPassword) -lvProfileList:($lvProfileList) -tbJumpCloudAPIKey:($tbJumpCloudAPIKey) } catch { $bMigrateProfile.IsEnabled = $false - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_apikey_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 $img_apikey_valid.ToolTip = "Please enter a valid JumpCloud API Key" $OrgSelection = "" $lbl_orgName.Text = "" - $img_apikey_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_apikey_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 Write-ToLog "MTP KEY MAY BE WRONG" } } @@ -613,14 +613,14 @@ Function Show-SelectionForm { If ((!(Test-IsNotEmpty $tbTempPassword.Text) -and (Test-HasNoSpace $tbTempPassword.Text)) -eq $false) { $tbTempPassword.Background = "#FFC6CBCF" $tbTempPassword.BorderBrush = "#FFF90000" - $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ErrorBase64 + $img_localaccount_password_valid.Source = Get-ImageFromB64 -ImageBase64 $ErrorBase64 $img_localaccount_password_valid.ToolTip = "Local Account Temp Password should not be empty or contain spaces, it should also meet local password policy req. on the system." } Else { $tbTempPassword.Background = "white" $tbTempPassword.Tooltip = $null $tbTempPassword.FontWeight = "Normal" $tbTempPassword.BorderBrush = "#FFC6CBCF" - $img_localaccount_password_valid.Source = DecodeBase64Image -ImageBase64 $ActiveBase64 + $img_localaccount_password_valid.Source = Get-ImageFromB64 -ImageBase64 $ActiveBase64 $img_localaccount_password_valid.ToolTip = $null } }) diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 index f470009d..060dfbed 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 @@ -9,7 +9,7 @@ function New-ProgressForm { $syncHash.StatusInput = '' $syncHash.LogText = @() $synchash.logLevel = '' - $syncHash.base64JCLogo = DecodeBase64Image -ImageBase64 $JCLogoBase64 + $syncHash.base64JCLogo = Get-ImageFromB64 -ImageBase64 $JCLogoBase64 $synchash.closeWindow = $false # Migration Details diff --git a/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 index d34873e1..48cae2be 100644 --- a/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Get-ImageFromB64.ps1 @@ -1,4 +1,4 @@ -function DecodeBase64Image { +function Get-ImageFromB64 { param ( [Parameter(Mandatory = $true)] [String]$ImageBase64 diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index cbbb6c5f..42de6cfc 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -932,13 +932,13 @@ Function Start-Migration { if ($AzureADStatus -eq 'YES' -or $netBiosName -match 'AzureAD') { # Find Appx User Apps by Username try { - $appxList = Get-AppXpackage -user (Convert-Sid $SelectedUserSID) | Select-Object InstallLocation + $appxList = Get-AppXpackage -user (Convert-SecurityIdentifier $SelectedUserSID) | Select-Object InstallLocation } catch { Write-ToLog -Message "Could not determine AppXPackages for selected user, this is okay. Rebuilding UWP Apps from AllUsers list" } } else { try { - $appxList = Get-AppXpackage -user (Convert-Sid $SelectedUserSID) | Select-Object InstallLocation + $appxList = Get-AppXpackage -user (Convert-SecurityIdentifier $SelectedUserSID) | Select-Object InstallLocation } catch { Write-ToLog -Message "Could not determine AppXPackages for selected user, this is okay. Rebuilding UWP Apps from AllUsers list" } diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index b1cfcc4c..96c11781 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -219,11 +219,11 @@ Describe 'Functions' { } } - Context 'Get-SID Function' { - It 'Tests that Get-SID returns a valid regex matched SID for the current user' { + Context 'Get-SecurityIdentifier Function' { + It 'Tests that Get-SecurityIdentifier returns a valid regex matched SID for the current user' { # SID of current user should match SID regex pattern $currentUser = $(whoami) -replace "$(hostname)\\", ("") - $currentSID = Get-SID -User:($currentUser) + $currentSID = Get-SecurityIdentifier -User:($currentUser) $currentSID | Should -Match "^S-\d-\d+-(\d+-){1,14}\d+$" } } @@ -231,7 +231,7 @@ Describe 'Functions' { Context 'Set-UserRegistryLoadState Function' -Skip { # Unload and load user registry - we are testing this in Migration tests It 'Load ' { - # $circlecisid = (Get-SID -User:'circleci') + # $circlecisid = (Get-SecurityIdentifier -User:'circleci') # Set-UserRegistryLoadState -op Load -ProfilePath 'C:\Users\circleci\' -UserSid $circlecisid # $path = 'HKU:\' $circlecisid_'_a # Test-Path -Path 'HKU:\$($circlecisid)' From 4df5df23e7f37bc3d8ea46105a0a7610f3a04754 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 8 Jan 2025 14:13:33 -0700 Subject: [PATCH 12/73] rename Build-Exe --- Deploy/{Build-Exe.ps1 => New-AdmuExe.ps1} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Deploy/{Build-Exe.ps1 => New-AdmuExe.ps1} (99%) diff --git a/Deploy/Build-Exe.ps1 b/Deploy/New-AdmuExe.ps1 similarity index 99% rename from Deploy/Build-Exe.ps1 rename to Deploy/New-AdmuExe.ps1 index d0e50be1..6f10cee9 100644 --- a/Deploy/Build-Exe.ps1 +++ b/Deploy/New-AdmuExe.ps1 @@ -61,7 +61,7 @@ $NewContent = $NewContent.Replace('$scriptPath = (Split-Path -Path:($MyInvocatio $NewContent = $NewContent.Replace('. ($scriptPath + ''\Start-Migration.ps1'')', $Functions) $NewContent = $NewContent.Replace('. ($scriptPath + ''\ProgressForm.ps1'')', $ProgressForm) $NewContent = $NewContent.Replace('$formResults = Invoke-Expression -Command:(''. "'' + $scriptPath + ''\Form.ps1"'')' + "`n", $Form) -$NewContent = $NewContent -replace('Return \$FormResults', '') +$NewContent = $NewContent -replace ('Return \$FormResults', '') $NewContent = $NewContent + "`n" $NewContent = $NewContent -split "`n" | ForEach-Object { If ($_.Trim()) { $_ From 129ef6bc81825c3e48f5bd1efa7a308a69d63019 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 13:12:12 -0700 Subject: [PATCH 13/73] 2.7.11 version + build module functions --- Deploy/Build-Module.ps1 | 4 +- Deploy/Build.ps1 | 22 ++- Deploy/BuildNuspecFromPsd1.ps1 | 3 +- Deploy/Functions/New-ADMUTemplate.ps1 | 129 ++++++++++++++ Deploy/Get-Config.ps1 | 3 +- Deploy/New-AdmuExe.ps1 | 132 ++++++-------- ModuleChangelog.md | 30 ++++ jumpcloud-ADMU/Docs/Start-Migration.md | 119 ++++++++----- jumpcloud-ADMU/JumpCloud.ADMU.psd1 | 163 +++++++++--------- jumpcloud-ADMU/JumpCloud.ADMU.psm1 | 22 ++- .../Powershell/Private/DisplayForms/Form.ps1 | 2 +- .../Private/DisplayForms/ProgressForm.ps1 | 2 +- .../Powershell/Public/Start-Migration.ps1 | 2 +- .../Powershell/Tests/Functions.Tests.ps1 | 14 +- jumpcloud-ADMU/en-US/JumpCloud.ADMU-help.xml | 36 ++++ 15 files changed, 459 insertions(+), 224 deletions(-) create mode 100644 Deploy/Functions/New-ADMUTemplate.ps1 diff --git a/Deploy/Build-Module.ps1 b/Deploy/Build-Module.ps1 index c4aeaa24..b4b0b4c2 100644 --- a/Deploy/Build-Module.ps1 +++ b/Deploy/Build-Module.ps1 @@ -25,7 +25,7 @@ if ($ManualModuleVersion) { $SemeanticVersion = Select-String -InputObject $ManualModuleVersionRetrieval -pattern ($SemanticRegex) $ModuleVersion = $SemeanticVersion[0].Matches.Value } else { - $PSGalleryInfo = Get-PSGalleryModuleVersion -Name:($ModuleName) -ReleaseType:($RELEASETYPE) #('Major', 'Minor', 'Patch') + $PSGalleryInfo = Get-PSGalleryModuleVersion -Name:($ModuleName) -ReleaseType:($ModuleVersionType) #('Major', 'Minor', 'Patch') $ModuleVersion = $PSGalleryInfo.NextVersion } Write-Host ('[status]PowerShell Gallery Name:' + $PSGalleryInfo.Name + ';CurrentVersion:' + $PSGalleryInfo.Version + '; NextVersion:' + $ModuleVersion ) @@ -48,7 +48,7 @@ New-ModuleManifest -Path:($FilePath_psd1) ` -FunctionsToExport:($Functions_Public.BaseName | Sort-Object) ` -RootModule:((Get-Item -Path:($FilePath_psm1)).Name) ` -ModuleVersion:($ModuleVersion) ` - -Author:('JumpCloud Solutions Architect Team') ` + -Author:('JumpCloud Customer Tools Team') ` -CompanyName:('JumpCloud') ` -Copyright:('(c) JumpCloud. All rights reserved.') ` -Description:('Powershell Module to run JumpCloud Active Directory Migration Utility.') diff --git a/Deploy/Build.ps1 b/Deploy/Build.ps1 index 79ffc263..91b43120 100644 --- a/Deploy/Build.ps1 +++ b/Deploy/Build.ps1 @@ -1,11 +1,18 @@ +## +# This script will: +# Run Build-Module (update changelog & validate versions across required files) +# Run Build-Exe (windows systems only) +# Build-NuspecFromPSD1 +## [CmdletBinding()] param ( - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateSet("Major", "Minor", "Patch", "Manual")] [System.string] $ModuleVersionType, [Parameter()] [System.string] - $ModuleName + $ModuleName = "JumpCloud.ADMU" ) # Run Get-Config: @@ -17,10 +24,13 @@ if ($ModuleVersionType -eq 'manual') { } else { . $PSScriptRoot\Build-Module.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) } - -# Run Build-Exe -. $PSScriptRoot\Build-Exe.ps1 +# Create a new ADMU Template file in this directory for testing/ Building EXE +New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" +# Run Build-Exe On Windows Systems +if ($IsWindows) { + . $PSScriptRoot\Build-Exe.ps1 +} # Run Build-HelpFiles . $PSScriptRoot\Build-HelpFiles.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) # Run Build-NuspecFromPsd1 -. $PSScriptRoot\BuildNuspecFromPsd1.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) +. $PSScriptRoot\BuildNuspecFromPsd1.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) \ No newline at end of file diff --git a/Deploy/BuildNuspecFromPsd1.ps1 b/Deploy/BuildNuspecFromPsd1.ps1 index 76ff506b..34c69706 100644 --- a/Deploy/BuildNuspecFromPsd1.ps1 +++ b/Deploy/BuildNuspecFromPsd1.ps1 @@ -20,7 +20,8 @@ If (-not $ADMUGetConfig) { $nuspecFiles = @( @{src = ".\en-Us\JumpCloud.ADMU-help.xml"; target = "./" }, - @{src = ".\Powershell\Start-Migration.ps1"; target = "./Public" }, + @{src = ".\Powershell\Private\**\*.ps1"; target = "./Private" }, + @{src = ".\Powershell\Public\**\*.ps1"; target = "./Public" }, @{src = ".\Docs\*.md"; target = "./Docs" }, @{src = ".\JumpCloud.ADMU.psd1" }, @{src = ".\JumpCloud.ADMU.psm1" } diff --git a/Deploy/Functions/New-ADMUTemplate.ps1 b/Deploy/Functions/New-ADMUTemplate.ps1 new file mode 100644 index 00000000..52917ccd --- /dev/null +++ b/Deploy/Functions/New-ADMUTemplate.ps1 @@ -0,0 +1,129 @@ +<# +.SYNOPSIS +This function will compile all the functions in this module into a single file so it can be built into an executable EXE. + +.DESCRIPTION +This function combines all the the private functions, forms, their assets and the public functions into a single file. + +.PARAMETER hidePowerShellWindow +This parameter optionally adds the code snippet to run the forms with or without the debug window. The default behavior is to hide these windows but when debugging it can be helpful to show these windows. + +#> +Function New-ADMUTemplate { + + [CmdletBinding()] + param ( + [Parameter( + HelpMessage = "When specified, this parameter will add or remove the code to hide the debug powershell window. By default this is set to `$true which will hide the powershell window when the code is executed." + )] + [bool] + $hidePowerShellWindow = $true, + [Parameter( + HelpMessage = "The path to export the file template." + )] + [System.String] + $ExportPath = "$PSScriptRoot/admuTemplate.ps1" + ) + begin { + # define empty string to build the template file + $templateString = "" + # Public Functions + $Public = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Public/*.ps1" -Recurse) + # Load all functions from private folders except for the forms and assets + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/*.ps1" -Recurse | Where-Object { ($_.fullname -notmatch "DisplayForms") -AND ($_.fullname -notmatch "DisplayAssets") } ) + } + process { + #define Run As Admin block + $adminString = @" +# Validate the user is an administrator +if (([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")) -eq `$false) { + Write-Host 'ADMU must be ran as an administrator.' + Read-Host -Prompt "Press Enter to exit" + exit +} +"@ + # add admin required string to template + $templateString += "$($adminString)" + [Environment]::NewLine + + # Define string for private functions + $PrivateFunctionsContent = "" + # add every private function to the new string + foreach ($item in $Private) { + $functionContent = Get-Content $item.FullName -Raw + $PrivateFunctionsContent += "$($functionContent)" + [Environment]::NewLine + } + + # Set the private region: + $privateFunctionsRegion = @" +## Region Private Functions ## +$PrivateFunctionsContent +## End Region Private Functions ## +"@ + # add private functions region to template + $templateString += $privateFunctionsRegion + [Environment]::NewLine + + # Define string for forms + $formsContent = "" + # Add Form Assets to template: + $Assets = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/DisplayAssets/*.ps1" -Recurse ) + foreach ($item in $Assets) { + $AssetContent = Get-Content $item.FullName -Raw + $formsContent += "$($AssetContent)" + [Environment]::NewLine + } + $Forms = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/DisplayForms/*.ps1" -Recurse ) + # Optionally hide debug window: + if ($hidePowerShellWindow) { + $hideRegion = @" +# Hides Powershell Window +`$ShowWindowAsync = Add-Type -MemberDefinition `@" + [DllImport("user32.dll")] + public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); +`"@ -Name "Win32ShowWindowAsync" -Namespace "Win32Functions" -PassThru +# PID of the current process +# Get PID of the current process +`$FormWindowPIDHandle = (Get-Process -Id $pid).MainWindowHandle +`$ShowWindowAsync::ShowWindowAsync(`$FormWindowPIDHandle, 0) | Out-Null +# PID +"@ + $formsContent += $hideRegion + [Environment]::NewLine + [Environment]::NewLine + } + # add each form file to the form string: + foreach ($item in $Forms) { + $FormContent = Get-Content $item.FullName -Raw + $formsContent += "$($FormContent)" + [Environment]::NewLine + } + + # define forms region: + $formsRegion = @" +## Region Forms ## +$formsContent +## End Region Forms ## +"@ + + # add the forms region to the template + $templateString += $formsRegion + [Environment]::NewLine + + # add each public function to the template: + foreach ($item in $Public) { + $functionContent = Get-Content $item.FullName -Raw + $templateString += "$($functionContent)" + [Environment]::NewLine + } + + # Define executable region + # endRegion + $executableRegion = @" +`$formResults = Show-SelectionForm +If (`$formResults) { + Start-Migration -inputObject:(`$formResults) +} Else { + Write-Output ('Exiting ADMU process') +} +"@ + # add executable region to the template + $templateString += $executableRegion + [Environment]::NewLine + } + end { + # write out the file + $templateString | Out-File $ExportPath -Force + } +} diff --git a/Deploy/Get-Config.ps1 b/Deploy/Get-Config.ps1 index 3b8ed1e5..5be2f253 100644 --- a/Deploy/Get-Config.ps1 +++ b/Deploy/Get-Config.ps1 @@ -89,7 +89,8 @@ If (!(Get-PackageProvider -Name:('NuGet') -ListAvailable -ErrorAction:('Silently } # Get module function names -$Functions_Public = @(Get-ChildItem -Path "$ModuleFolderName/Powershell/Start-Migration.ps1") +$Functions_Public = @(Get-ChildItem -Path "$ModuleFolderName/Powershell/Public/" -Recurse) +$Functions_Private = @(Get-ChildItem -Path "$ModuleFolderName/Powershell/Private/" -Recurse) # Import module in development Write-Host ('Importing module: ' + $FilePath_psd1) diff --git a/Deploy/New-AdmuExe.ps1 b/Deploy/New-AdmuExe.ps1 index 6f10cee9..540099b3 100644 --- a/Deploy/New-AdmuExe.ps1 +++ b/Deploy/New-AdmuExe.ps1 @@ -1,3 +1,10 @@ +[CmdletBinding()] +param ( + [Parameter()] + [System.String] + $ModuleName = "JumpCloud.ADMU" +) +# This script will build a new JumpCloud EXE File Write-Host "======= Begin Build-Exe =======" If (-not $ADMUGetConfig) { @@ -10,7 +17,7 @@ $ModuleChangelogVersionRegex = "([0-9]+)\.([0-9]+)\.([0-9]+)" $ModuleChangelogVersionMatch = ($ModuleChangelog | Select-Object -First 1) | Select-String -Pattern:($ModuleChangelogVersionRegex) $ModuleChangelogVersion = $ModuleChangelogVersionMatch.Matches.Value # Form.ps1 Variables -$FormPath = $FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Form.ps1' +$FormPath = $FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Private\DisplayForms\Form.ps1' $VersionFormRegex = [regex]'(?<=Title="JumpCloud ADMU )([0-9]+)\.([0-9]+)\.([0-9]+)' $VersionMatchForm = Select-String -Path:($FormPath) -Pattern:($VersionFormRegex) $FormVersion = $VersionMatchForm.Matches.Value @@ -22,7 +29,6 @@ $PSD1Version = $VersionMatchPsd1.Matches.Value # ADMU.ps1 variables $year = Get-Date -Format "yyyy" -$Output = $FolderPath_ModuleRootPath + '\Deploy\ADMU.ps1' # Write out diagnostic information Write-Host "[JumpCloud ADMU Build Configuration]" @@ -33,87 +39,57 @@ Write-Host "Psd1 Version: $PSD1Version" $FormVersion | Should -Be $PSD1Version # Build ADMU.PS1 File: +. $PSScriptRoot\New-ADMUTemplate.ps1 +New-ADMUTemplate # Clear existing file -If (Test-Path -Path:($Output)) { - Remove-Item -Path:($Output) +If (Test-Path -Path:("$PSScriptRoot/admuTemplate.ps1")) { + "A Template file exists" +} else { + throw "A template file does not exist, an EXE can not be built." } -# Get file contents -$StartJCADMU = (Get-Content -Path:($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Start-JCADMU.ps1') -Raw) -Replace ("`r", "") -$Functions = (Get-Content -Path:($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Start-Migration.ps1') -Raw) -Replace ("`r", "") -$ProgressForm = (Get-Content -Path:($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\ProgressForm.ps1') -Raw) -Replace ("`r", "") -$Form = (Get-Content -Path:($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Form.ps1') -Raw) -Replace ("`r", "") - -# TODO: Add Private functions to $NewContent. This code is commented out for later use. -# Get file content of /jumpcloud-ADMU/Powershell/Private -#$PrivateFolder = Get-ChildItem -Path:($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Private\') -# String manipulation -# Iterate through each file in the Private folder and append to $Functions -# ForEach ($File in $PrivateFolder) { -# $PrivateFunctions += (Get-Content -Path:($File.FullName) -Raw) -Replace ("`r", "") -# } -#$NewContent = $PrivateFunctions -$NewContent = $StartJCADMU -# Add Private functions to $NewContent - -$NewContent = $NewContent.Replace('# Get script path' + "`n", '') -$NewContent = $NewContent.Replace('$scriptPath = (Split-Path -Path:($MyInvocation.MyCommand.Path))' + "`n", '') -$NewContent = $NewContent.Replace('. ($scriptPath + ''\Start-Migration.ps1'')', $Functions) -$NewContent = $NewContent.Replace('. ($scriptPath + ''\ProgressForm.ps1'')', $ProgressForm) -$NewContent = $NewContent.Replace('$formResults = Invoke-Expression -Command:(''. "'' + $scriptPath + ''\Form.ps1"'')' + "`n", $Form) -$NewContent = $NewContent -replace ('Return \$FormResults', '') -$NewContent = $NewContent + "`n" -$NewContent = $NewContent -split "`n" | ForEach-Object { If ($_.Trim()) { - $_ - } } -Write-Host "[Status] Building new ADMU.ps1 file from Start-JCADMU, Form, and, Start-Migration scripts" -$NewContent | Out-File -FilePath:($Output) -If (-not [System.String]::IsNullOrEmpty($NewContent)) { - $NewContent | Out-File -FilePath:($Output) +# TODO: double check but 1.0.15 should be good to run on core versions of PWSH +# Get PSVersion Table PS2EXE can only run in pwsh shell +# $PSVersion = $PSVersionTable +# If ($PSVersion.PSEdition -eq "Core") { +# Write-Warning "Building ADMU exe binary files requires PowerShell Non-Core edition and a windows host" +# } else { +If (-Not (Get-InstalledModule -Name ps2exe -ErrorAction Ignore)) { + Install-Module -Name ps2exe -RequiredVersion '1.0.13' -force +} +Import-Module -Name ps2exe +If (-not [System.String]::IsNullOrEmpty($PSD1Version)) { + $guiOutputPath = ($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\exe\gui_jcadmu.exe') + Invoke-ps2exe -inputFile "$PSScriptRoot/admuTemplate.ps1" -outputFile $guiOutputPath -title 'JumpCloud ADMU' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -requireAdmin -iconfile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') + $guiExeFile = Get-Item $guiOutputPath + $guiHash = (get-filehash -algorithm SHA256 -path $guiExeFile).Hash + Write-Host "==== GUI_JCADMU.EXE Build Status ====" + Write-Host "Version: $($guiExeFile.VersionInfo.FileVersionRaw)" + Write-Host "Build Date: $($guiExeFile.CreationTime)" + Write-Host "Size (bytes): $($guiExeFile.Length)" + Write-Host "SHA256 Hash: $guiHash" + Write-Host "gui_jcadmu.exe was generated successfully" } Else { - Write-Error ('Build-Exe.ps1 failed. Transform process outputted an empty ADMU.ps1 file.') + Write-Error ('Unable to find version number in "' + $PSD1Path + '" using regex "' + $VersionPsd1Regex + '"') + throw "gui_jcadmu.exe was not generated" } -# Get PSVersion Table PS2EXE can only run in pwsh shell -$PSVersion = $PSVersionTable -If ($PSVersion.PSEdition -eq "Core") { - Write-Warning "Building ADMU exe binary files requires PowerShell Non-Core edition and a windows host" -} else { - If (-Not (Get-InstalledModule -Name ps2exe -ErrorAction Ignore)) { - Install-Module -Name ps2exe -RequiredVersion '1.0.13' -force - } - Import-Module -Name ps2exe - If (-not [System.String]::IsNullOrEmpty($PSD1Version)) { - $guiOutputPath = ($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\exe\gui_jcadmu.exe') - Invoke-ps2exe -inputFile $Output -outputFile $guiOutputPath -title 'JumpCloud ADMU' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -requireAdmin -iconfile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') - $guiExeFile = Get-Item $guiOutputPath - $guiHash = (get-filehash -algorithm SHA256 -path $guiExeFile).Hash - Write-Host "==== GUI_JCADMU.EXE Build Status ====" - Write-Host "Version: $($guiExeFile.VersionInfo.FileVersionRaw)" - Write-Host "Build Date: $($guiExeFile.CreationTime)" - Write-Host "Size (bytes): $($guiExeFile.Length)" - Write-Host "SHA256 Hash: $guiHash" - Write-Host "gui_jcadmu.exe was generated successfully" - } Else { - Write-Error ('Unable to find version number in "' + $PSD1Path + '" using regex "' + $VersionPsd1Regex + '"') - throw "gui_jcadmu.exe was not generated" - } - $uwpPath = $FolderPath_ModuleRootPath + '\Deploy\uwp_jcadmu.ps1' - # Always generate a new UWP EXE - try { - $uwpOutputPath = ($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\exe\uwp_jcadmu.exe') - Invoke-ps2exe -inputFile ($uwpPath) -outputFile $uwpOutputPath -title 'JumpCloud ADMU UWP Fix' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility UWP Fix Executable' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -iconfile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') - $uwpExeFile = Get-Item $uwpOutputPath - $uwpHash = (get-filehash -algorithm SHA256 -path $uwpExeFile).Hash - Write-Host "==== UWP_JCADMU.EXE Build Status ====" - Write-Host "Version: $($uwpExeFile.VersionInfo.FileVersionRaw)" - Write-Host "Build Date: $($uwpExeFile.CreationTime)" - Write-Host "Size (bytes): $($uwpExeFile.Length)" - Write-Host "SHA256 Hash: $uwpHash" - Write-Host "upw_jcadmu.exe was generated successfully" - } catch { - Write-Error ('Unable to find version number in "' + $PSD1Path + '" using regex "' + $VersionPsd1Regex + '"') - Throw "upw_jcadmu.exe was not generated" - } +$uwpPath = $FolderPath_ModuleRootPath + '\Deploy\uwp_jcadmu.ps1' +# Always generate a new UWP EXE +try { + $uwpOutputPath = ($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\exe\uwp_jcadmu.exe') + Invoke-ps2exe -inputFile ($uwpPath) -outputFile $uwpOutputPath -title 'JumpCloud ADMU UWP Fix' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility UWP Fix Executable' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -iconfile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') + $uwpExeFile = Get-Item $uwpOutputPath + $uwpHash = (get-filehash -algorithm SHA256 -path $uwpExeFile).Hash + Write-Host "==== UWP_JCADMU.EXE Build Status ====" + Write-Host "Version: $($uwpExeFile.VersionInfo.FileVersionRaw)" + Write-Host "Build Date: $($uwpExeFile.CreationTime)" + Write-Host "Size (bytes): $($uwpExeFile.Length)" + Write-Host "SHA256 Hash: $uwpHash" + Write-Host "upw_jcadmu.exe was generated successfully" +} catch { + Write-Error ('Unable to find version number in "' + $PSD1Path + '" using regex "' + $VersionPsd1Regex + '"') + Throw "upw_jcadmu.exe was not generated" } +# } Write-Host "======= End Build-Exe =======" diff --git a/ModuleChangelog.md b/ModuleChangelog.md index 7a6a25a6..18bf7456 100644 --- a/ModuleChangelog.md +++ b/ModuleChangelog.md @@ -1,31 +1,57 @@ +## 2.7.11 + +Release Date: January 13, 2025 + +#### RELEASE NOTES + +``` +This release addresses some code quality issues and a bug-fix for certain Windows 11 systems where migrated users could lost access to use Windows search post-migration. +``` + +#### IMPROVEMENTS: + +- Functions in this release are now broken up into individual files to de-clutter the Start-Migration script + +#### BUG FIXES: + +- Windows 11 systems with a specific build and Microsoft KB installed could lose access to the Windows Start search menu post-migration, this release applies the same fix in 2.4.3 (At the time then only affected Windows 10 systems) to Windows 11 systems. + ## 2.7.10 Release Date: January 3, 2025 #### RELEASE NOTES + ``` * This release prevents ADMU from migrating if one of the main user folders (Desktop, Downloads, Documents, Pictures, Music, Videos, Favorites) are redirected to network shared path ``` + #### Bug Fixes: + ``` * Fix issue when migrating a user with one of their main user folders are redirected to a network path. ADMU will now throw an error and prevent migration if any of the primary user folders (Desktop, Downloads, Documents, Pictures, Music, Videos, Favorites) are redirected to network shared path ``` + ## 2.7.9 Release Date: November 21, 2024 #### RELEASE NOTES + ``` * This release removes 40 char API key validation * When the migration fails during the account copy/merge processes, the tool would revert and remove the newly created account. We risk deleting user data once we do the account reversal in this step. To combat this, we have added a tracker to not remove the created account profile during account merge failure. * Remove unused .exe files ``` + #### Bug Fixes: + ``` * Fix progress form buttons disabled when JCAgent install fails * Fix issue with JCUsername that have a localUsername where progress form GUI get's stuck during migration when AutoBind is selected * Fix issue with MTP selection popups when migrating a user that belongs to an MTP ``` + ## 2.7.8 Release Date: October 14, 2024 @@ -50,9 +76,11 @@ Release Date: September 25, 2024 This release resolves an issue on Windows 10 systems where users were unable to use the search bar post-migration #### Bug Fixes: + ``` * Resolves an issue on Windows 10 systems where users were unable to use the search bar post-migration ``` + ## 2.7.6 Release Date: August 21, 2024 @@ -62,9 +90,11 @@ Release Date: August 21, 2024 This fixes an issue with disabled Migrate Button #### Bug Fixes: + ``` * Fixed an issue with ADMU UI "Migrate Profile" button where it remained disabled even though all the required fields were satisfied. ``` + ## 2.7.5 Release Date: August 28, 2024 diff --git a/jumpcloud-ADMU/Docs/Start-Migration.md b/jumpcloud-ADMU/Docs/Start-Migration.md index 48bfdd94..7cbb3fb3 100644 --- a/jumpcloud-ADMU/Docs/Start-Migration.md +++ b/jumpcloud-ADMU/Docs/Start-Migration.md @@ -8,13 +8,11 @@ schema: 2.0.0 # Start-Migration ## SYNOPSIS - Starts the JumpCloud Active Directory Migration process. ## SYNTAX ### cmd - ``` Start-Migration -JumpCloudUserName -SelectedUserName -TempPassword [-LeaveDomain ] [-ForceReboot ] [-UpdateHomePath ] [-InstallJCAgent ] @@ -24,38 +22,43 @@ Start-Migration -JumpCloudUserName -SelectedUserName -TempPass ``` ### form - ``` Start-Migration [-inputObject ] [-ProgressAction ] [] ``` ## DESCRIPTION - -The Start-Migration function allows the starting of the JumpCloud Active Directory Migration Process. This utility can be used to convert domain bound user accounts into locally managed accounts ready to be taken over by JumpCloud. There are various options to run the utility depending on the administrators requirements. +The Start-Migration function allows the starting of the JumpCloud Active Directory Migration Process. +This utility can be used to convert domain bound user accounts into locally managed accounts ready to be taken over by JumpCloud. +There are various options to run the utility depending on the administrators requirements. ## EXAMPLES ### Example 1 - -```powershell +``` PS C:\> Start-Migration -SelectedUserName 'DOMAIN\bobfay' -JumpCloudUserName 'bob.fay' -TempPassword 'Temp123!Temp123!' -LeaveDomain $true -ForceReboot $true -InstallJCAgent $true -JumpCloudConnectKey 'ConnectKEY' -AutobindJCUser $true -JumpCloudAPIKey 'APIKEY' ``` -This example would run the `Start-Migration` function on a domain user `DOMAIN\bobfay` and create a new local user account `COMPUTERNAME\bob.fay`. Using a temporary password `Temp123!Temp123!`. The JumpCloud Agent would be installed. After migration the JumpCloud user `bob.fay` would be bound to the system. Finally, the system would leave the bound domain and reboot. +This example would run the \`Start-Migration\` function on a domain user \`DOMAIN\bobfay\` and create a new local user account \`COMPUTERNAME\bob.fay\`. +Using a temporary password \`Temp123!Temp123!\`. +The JumpCloud Agent would be installed. +After migration the JumpCloud user \`bob.fay\` would be bound to the system. +Finally, the system would leave the bound domain and reboot. ### Example 2 - -```powershell +``` PS C:\> Start-Migration -SelectedUserName 'DOMAIN\bobfay' -JumpCloudUserName 'bob.fay' -TempPassword 'Temp123!Temp123!' $false -ForceReboot $false -InstallJCAgent $false ``` -This example would run the `Start-Migration` function on a domain user `DOMAIN\jsmith` and create a new local user account `COMPUTERNAME\john.smith`. Using a temporary password `Temp123!Temp123!`, the system would remain bound to the current domain and not reboot. The JumpCloud Agent would not be installed. This would allow the administrator to run the converted account in parallel for testing. +This example would run the \`Start-Migration\` function on a domain user \`DOMAIN\jsmith\` and create a new local user account \`COMPUTERNAME\john.smith\`. +Using a temporary password \`Temp123!Temp123!\`, the system would remain bound to the current domain and not reboot. +The JumpCloud Agent would not be installed. +This would allow the administrator to run the converted account in parallel for testing. ## PARAMETERS ### -ForceReboot - -A boolean $true/$false value to force the system to reboot at the end of the migration process. A reboot is required when unbinding from a domain. +A boolean $true/$false value to force the system to reboot at the end of the migration process. +A reboot is required when unbinding from a domain. ```yaml Type: System.Boolean @@ -70,8 +73,9 @@ Accept wildcard characters: False ``` ### -InstallJCAgent - -A boolean $true/$false value to install the JumpCloud agent on the system. If this value is $true you will be required to also pass a `JumpCloudConnectKey` value. If the system remains on the domain, the JumpCloud agent will be installed but not connected until it leaves the domain. +A boolean $true/$false value to install the JumpCloud agent on the system. +If this value is $true you will be required to also pass a \`JumpCloudConnectKey\` value. +If the system remains on the domain, the JumpCloud agent will be installed but not connected until it leaves the domain. ```yaml Type: System.Boolean @@ -86,8 +90,9 @@ Accept wildcard characters: False ``` ### -JumpCloudConnectKey - -A string value that is required if `-InstallJCAgent` is $true. This connect key can be found in the JumpCloud console under add systems. It must be 24 chars and is different than an JumpCloud API key. +A string value that is required if \`-InstallJCAgent\` is $true. +This connect key can be found in the JumpCloud console under add systems. +It must be 24 chars and is different than an JumpCloud API key. ```yaml Type: System.String @@ -102,8 +107,9 @@ Accept wildcard characters: False ``` ### -JumpCloudUserName - -A string value that will be used for the new converted local account that can be bound to JumpCloud. This value must be unique on the system, if it is not unique an error will stop the migration. This value should match the JumpCloud Username value to allow takeover when a User is bound to a system within the JumpCloud console. +A string value that will be used for the new converted local account that can be bound to JumpCloud. +This value must be unique on the system, if it is not unique an error will stop the migration. +This value should match the JumpCloud Username value to allow takeover when a User is bound to a system within the JumpCloud console. ```yaml Type: System.String @@ -118,8 +124,9 @@ Accept wildcard characters: False ``` ### -LeaveDomain - -A boolean $true/$false value to force the system to leave currently bound domain, this is required for the JumpCloud Agent to operate. It can also be reversed by simply rejoining the system back to the domain. This will also work for AzureAD and will disconnect the AzureAD bind. +A boolean $true/$false value to force the system to leave currently bound domain, this is required for the JumpCloud Agent to operate. +It can also be reversed by simply rejoining the system back to the domain. +This will also work for AzureAD and will disconnect the AzureAD bind. ```yaml Type: System.Boolean @@ -134,8 +141,9 @@ Accept wildcard characters: False ``` ### -TempPassword - -A string value that is used to set the new local accounts password. When duplicating the user account a password must be set when created and this value is passed. Once the system is online in JumpCloud the password will be overwritten and synced with JumpCloud if the user is taken over. +A string value that is used to set the new local accounts password. +When duplicating the user account a password must be set when created and this value is passed. +Once the system is online in JumpCloud the password will be overwritten and synced with JumpCloud if the user is taken over. ```yaml Type: System.String @@ -150,8 +158,8 @@ Accept wildcard characters: False ``` ### -inputObject - -An PSObject can be passed to the function with the required values for the migration process. This is used when the GUI version of the tool is used and inputs to the XAML form are passed to this function. +An PSObject can be passed to the function with the required values for the migration process. +This is used when the GUI version of the tool is used and inputs to the XAML form are passed to this function. ```yaml Type: System.Object @@ -166,8 +174,10 @@ Accept wildcard characters: False ``` ### -SelectedUserName - -A string value for the DomainUserName that is used in the migration script. This value is verified to make sure the account exists on the system. If the Domain Account does not exist, the script will error and not continue. Either pass a username using the "Domain\username" syntax or a domain user SID. +A string value for the DomainUserName that is used in the migration script. +This value is verified to make sure the account exists on the system. +If the Domain Account does not exist, the script will error and not continue. +Either pass a username using the "Domain\username" syntax or a domain user SID. ```yaml Type: System.String @@ -182,8 +192,7 @@ Accept wildcard characters: False ``` ### -AutobindJCUser - -This parameter will bind the username specified in the `JumpCloudUserName` field to the current system after Migration. +This parameter will bind the username specified in the \`JumpCloudUserName\` field to the current system after Migration. ```yaml Type: System.Boolean @@ -198,8 +207,9 @@ Accept wildcard characters: False ``` ### -UpdateHomePath - -If set to $true, the ADMU will attempt to rename the selected username's homepath to the jumpcloud username. Note, this could break any applications that rely on a hard coded homepath. By default this is not set and will not rename the homepath. +If set to $true, the ADMU will attempt to rename the selected username's homepath to the jumpcloud username. +Note, this could break any applications that rely on a hard coded homepath. +By default this is not set and will not rename the homepath. ```yaml Type: System.Boolean @@ -214,8 +224,8 @@ Accept wildcard characters: False ``` ### -JumpCloudAPIKey - -The Read/Write API key of a JumpCloud Administrator. This parameter is required if the AutoBind JC User parameter is specified. +The Read/Write API key of a JumpCloud Administrator. +This parameter is required if the AutoBind JC User parameter is specified. ```yaml Type: System.String @@ -230,8 +240,8 @@ Accept wildcard characters: False ``` ### -JumpCloudOrgID - -The ID of the JumpCloud Organization you wish to connect to. This field is only required if an MTP Api Key is used in the JumpCloudApiKey Parameter +The ID of the JumpCloud Organization you wish to connect to. +This field is only required if an MTP Api Key is used in the JumpCloudApiKey Parameter ```yaml Type: System.String @@ -246,8 +256,9 @@ Accept wildcard characters: False ``` ### -BindAsAdmin - -Option to bind user as sudo administrator or not. This parameter is not required and will default to $false (User will not be bound as admin). Set to $true if you'd like to bind the JumpCloudUserName as administrator during migration. +Option to bind user as sudo administrator or not. +This parameter is not required and will default to $false (User will not be bound as admin). +Set to $true if you'd like to bind the JumpCloudUserName as administrator during migration. ```yaml Type: System.Boolean @@ -262,8 +273,9 @@ Accept wildcard characters: False ``` ### -SetDefaultWindowsUser - -Option to set the windows default login user to the migrated user post-migration. This parameter is not required and will default to $true (the next login window user will be the migrated user). Set to $false if you'd like to disable this functionality during migration. +Option to set the windows default login user to the migrated user post-migration. +This parameter is not required and will default to $true (the next login window user will be the migrated user). +Set to $false if you'd like to disable this functionality during migration. ```yaml Type: System.Boolean @@ -278,8 +290,8 @@ Accept wildcard characters: False ``` ### -AdminDebug - -Option to display detailed messages during migration. This parameter is optional, but if set to $true, the CLI will show verbose output during the migration process +Option to display detailed messages during migration. +This parameter is optional, but if set to $true, the CLI will show verbose output during the migration process ```yaml Type: System.Boolean @@ -294,8 +306,10 @@ Accept wildcard characters: False ``` ### -ValidateUserShellFolder - -Option to bypass User Shell Folder validation. When set to `$false`, the migration will not verify whether folders (Desktop, Downloads, Documents, Videos, Pictures, Music, Favorites) are redirected to another location, such as a network shared folder (e.g., `\\192.168.50.78\SharedFolder\USERNAME\Desktop`). Use this parameter with caution. After migration, the user may encounter a shared folder error and will need to provide account credentials to restore their shared folders +Option to bypass User Shell Folder validation. +When set to \`$false\`, the migration will not verify whether folders (Desktop, Downloads, Documents, Videos, Pictures, Music, Favorites) are redirected to another location, such as a network shared folder (e.g., \`\192.168.50.78\SharedFolder\USERNAME\Desktop\`). +Use this parameter with caution. +After migration, the user may encounter a shared folder error and will need to provide account credentials to restore their shared folders ```yaml Type: System.Boolean @@ -309,20 +323,33 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### CommonParameters +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: System.Management.Automation.ActionPreference +Parameter Sets: (All) +Aliases: proga +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS ### None - ## OUTPUTS ### System.Object - ## NOTES ## RELATED LINKS [https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/Start-Migration](https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/Start-Migration) + diff --git a/jumpcloud-ADMU/JumpCloud.ADMU.psd1 b/jumpcloud-ADMU/JumpCloud.ADMU.psd1 index 48ff51b4..60f105a7 100644 --- a/jumpcloud-ADMU/JumpCloud.ADMU.psd1 +++ b/jumpcloud-ADMU/JumpCloud.ADMU.psd1 @@ -1,133 +1,132 @@ # # Module manifest for module 'JumpCloud.ADMU' # -# Generated by: JumpCloud Solutions Architect Team +# Generated by: JumpCloud Customer Tools Team # -# Generated on: 7/21/2024 +# Generated on: 1/13/2025 # @{ - # Script module or binary module file associated with this manifest. - RootModule = 'JumpCloud.ADMU.psm1' +# Script module or binary module file associated with this manifest. +RootModule = 'JumpCloud.ADMU.psm1' - # Version number of this module. +# Version number of this module. +ModuleVersion = '2.7.11' - ModuleVersion = '2.7.10' +# Supported PSEditions +# CompatiblePSEditions = @() - # Supported PSEditions - # CompatiblePSEditions = @() +# ID used to uniquely identify this module +GUID = '1c69386e-055a-4e5b-801e-0a6ccf714d6e' - # ID used to uniquely identify this module - GUID = 'dc1c3ff9-b5dd-4f05-9f90-3da45b2ea28b' +# Author of this module +Author = 'JumpCloud Customer Tools Team' - # Author of this module - Author = 'JumpCloud Solutions Architect Team' +# Company or vendor of this module +CompanyName = 'JumpCloud' - # Company or vendor of this module - CompanyName = 'JumpCloud' +# Copyright statement for this module +Copyright = '(c) JumpCloud. All rights reserved.' - # Copyright statement for this module - Copyright = '(c) JumpCloud. All rights reserved.' +# Description of the functionality provided by this module +Description = 'Powershell Module to run JumpCloud Active Directory Migration Utility.' - # Description of the functionality provided by this module - Description = 'Powershell Module to run JumpCloud Active Directory Migration Utility.' +# Minimum version of the PowerShell engine required by this module +# PowerShellVersion = '' - # Minimum version of the PowerShell engine required by this module - # PowerShellVersion = '' +# Name of the PowerShell host required by this module +# PowerShellHostName = '' - # 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 = @() - # Modules that must be imported into the global environment prior to importing this module - # RequiredModules = @() +# 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 = 'Start-Migration' - # 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 = 'Start-Migration' +# 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 = '*' - # 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 = '*' +# 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 = @() - # Tags applied to this module. These help with module discovery in online galleries. - # Tags = @() + # A URL to the license for this module. + # LicenseUri = '' - # A URL to the license for this module. - # LicenseUri = '' + # A URL to the main website for this project. + # ProjectUri = '' - # A URL to the main website for this project. - # ProjectUri = '' + # A URL to an icon representing this module. + # IconUri = '' - # A URL to an icon representing this module. - # IconUri = '' + # ReleaseNotes of this module + # ReleaseNotes = '' - # ReleaseNotes of this module - # 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 = '' - # HelpInfo URI of this module - # HelpInfoURI = '' - - # 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/jumpcloud-ADMU/JumpCloud.ADMU.psm1 b/jumpcloud-ADMU/JumpCloud.ADMU.psm1 index fce4dcbf..d122dc46 100644 --- a/jumpcloud-ADMU/JumpCloud.ADMU.psm1 +++ b/jumpcloud-ADMU/JumpCloud.ADMU.psm1 @@ -1,5 +1,23 @@ -$Public = @(Get-ChildItem -Path "$PSScriptRoot\Powershell\Start-Migration.ps1") +# Load all functions from private folders +$Private = @( Get-ChildItem -Path "$PSScriptRoot/Powershell/Private/*.ps1" -Recurse) +Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } +} -. "$PSScriptRoot\Powershell\Start-Migration.ps1" +# Load all public functions: +$Public = @( Get-ChildItem -Path "$PSScriptRoot/Powershell/Public/*.ps1" -Recurse) +Foreach ($Import in $Public) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } +} + +# . "$PSScriptRoot\Powershell\Start-Migration.ps1" Export-ModuleMember -Function $Public.BaseName diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 index 36ae09a6..8d6097a7 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 @@ -10,7 +10,7 @@ Function Show-SelectionForm { diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 index 060dfbed..d6603c2b 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/ProgressForm.ps1 @@ -22,7 +22,7 @@ function New-ProgressForm { diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index 42de6cfc..1d78133a 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -35,7 +35,7 @@ Function Start-Migration { $AGENT_INSTALLER_URL = "https://cdn02.jumpcloud.com/production/jcagent-msi-signed.msi" $AGENT_INSTALLER_PATH = "$windowsDrive\windows\Temp\JCADMU\jcagent-msi-signed.msi" $AGENT_CONF_PATH = "$($AGENT_PATH)\Plugins\Contrib\jcagent.conf" - $admuVersion = '2.7.10' + $admuVersion = '2.7.11' # Log Windows System Version Information Write-ToLog -Message:("OSName: $($systemVersion.OSName), OSVersion: $($systemVersion.OSVersion), OSBuildNumber: $($systemVersion.OsBuildNumber), OSEdition: $($systemVersion.WindowsEditionId)") diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index 96c11781..8c678d7e 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -4,15 +4,23 @@ BeforeAll { . $PSScriptRoot\..\..\..\Deploy\TestSetup.ps1 -TestOrgConnectKey $env:PESTER_CONNECTKEY } Write-Host "Script Location: $PSScriptRoot" - Write-Host "Dot-Sourcing Start-Migration Script" - . $PSScriptRoot\..\Start-Migration.ps1 - Write-Host "Dot-Sourcing Test Functions" + # setting test variables . $PSScriptRoot\SetupAgent.ps1 Write-Host "Running Connect-JCOnline" Connect-JCOnline -JumpCloudApiKey $env:PESTER_APIKEY -JumpCloudOrgId $env:PESTER_ORGID -Force Function Get-WindowsDrive { return 'drive' } + # TODO: import private functions: + Write-Host "Dot-Sourcing Private Functions" + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } } Describe 'Functions' { BeforeAll { diff --git a/jumpcloud-ADMU/en-US/JumpCloud.ADMU-help.xml b/jumpcloud-ADMU/en-US/JumpCloud.ADMU-help.xml index 4aa55e99..212b135f 100644 --- a/jumpcloud-ADMU/en-US/JumpCloud.ADMU-help.xml +++ b/jumpcloud-ADMU/en-US/JumpCloud.ADMU-help.xml @@ -195,6 +195,18 @@ None + + ProgressAction + + {{ Fill ProgressAction Description }} + + System.Management.Automation.ActionPreference + + System.Management.Automation.ActionPreference + + + None + Start-Migration @@ -210,6 +222,18 @@ None + + ProgressAction + + {{ Fill ProgressAction Description }} + + System.Management.Automation.ActionPreference + + System.Management.Automation.ActionPreference + + + None + @@ -405,6 +429,18 @@ None + + ProgressAction + + {{ Fill ProgressAction Description }} + + System.Management.Automation.ActionPreference + + System.Management.Automation.ActionPreference + + + None + From 8d2d16acffab86d8737a946a428b42d1c61b74d3 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 13:36:46 -0700 Subject: [PATCH 14/73] change patch for new-admuTemplate --- Deploy/Functions/New-ADMUTemplate.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Deploy/Functions/New-ADMUTemplate.ps1 b/Deploy/Functions/New-ADMUTemplate.ps1 index 52917ccd..56f49d4f 100644 --- a/Deploy/Functions/New-ADMUTemplate.ps1 +++ b/Deploy/Functions/New-ADMUTemplate.ps1 @@ -28,9 +28,9 @@ Function New-ADMUTemplate { # define empty string to build the template file $templateString = "" # Public Functions - $Public = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Public/*.ps1" -Recurse) + $Public = @( Get-ChildItem -Path "$PSScriptRoot/../../jumpcloud-ADMU/Powershell/Public/*.ps1" -Recurse) # Load all functions from private folders except for the forms and assets - $Private = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/*.ps1" -Recurse | Where-Object { ($_.fullname -notmatch "DisplayForms") -AND ($_.fullname -notmatch "DisplayAssets") } ) + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../../jumpcloud-ADMU/Powershell/Private/*.ps1" -Recurse | Where-Object { ($_.fullname -notmatch "DisplayForms") -AND ($_.fullname -notmatch "DisplayAssets") } ) } process { #define Run As Admin block @@ -65,12 +65,12 @@ $PrivateFunctionsContent # Define string for forms $formsContent = "" # Add Form Assets to template: - $Assets = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/DisplayAssets/*.ps1" -Recurse ) + $Assets = @( Get-ChildItem -Path "$PSScriptRoot/../../jumpcloud-ADMU/Powershell/Private/DisplayAssets/*.ps1" -Recurse ) foreach ($item in $Assets) { $AssetContent = Get-Content $item.FullName -Raw $formsContent += "$($AssetContent)" + [Environment]::NewLine } - $Forms = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/DisplayForms/*.ps1" -Recurse ) + $Forms = @( Get-ChildItem -Path "$PSScriptRoot/../../jumpcloud-ADMU/Powershell/Private/DisplayForms/*.ps1" -Recurse ) # Optionally hide debug window: if ($hidePowerShellWindow) { $hideRegion = @" From 70f387e1988be2cc2f53e9e665ce90f71530d6eb Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 13:45:40 -0700 Subject: [PATCH 15/73] pester tests --- jumpcloud-ADMU/Powershell/InvokePester.ps1 | 3 --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/InvokePester.ps1 b/jumpcloud-ADMU/Powershell/InvokePester.ps1 index fdf9b834..cd2580a4 100644 --- a/jumpcloud-ADMU/Powershell/InvokePester.ps1 +++ b/jumpcloud-ADMU/Powershell/InvokePester.ps1 @@ -5,9 +5,6 @@ param ( $ModuleVersionType ) $env:ModuleVersionType = $ModuleVersionType -# Load functions -. "$PSScriptRoot/Start-Migration.ps1" - # Import pester module $PesterInstalledVersion = Get-InstalledModule -Name Pester diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 352ed4cd..95e31397 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -19,8 +19,8 @@ BeforeAll { write-host "Importing Build Variables:" . $PSScriptRoot\BuildVariables.ps1 # import functions from start migration - write-host "Importing Start-Migration Script:" - . $PSScriptRoot\..\Start-Migration.ps1 + write-host "Importing Start-Migration Module:" + Import-Module "$PSScriptRoot/../JumpCloud.ADMU.psd1" -Force # setup tests (This creates any of the users in the build vars dictionary) write-host "Running SetupAgent Script:" . $PSScriptRoot\SetupAgent.ps1 From 3595d226e4b9ca21e8d74017bc2da5dc66d7ce68 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 14:26:12 -0700 Subject: [PATCH 16/73] CI changes for functions location --- .github/workflows/admu-ci.yml | 5 +++-- .github/workflows/admu-release.yml | 2 +- Deploy/BuildNuspecFromPsd1.ps1 | 4 ++-- jumpcloud-ADMU/JumpCloud.ADMU.psd1 | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/admu-ci.yml b/.github/workflows/admu-ci.yml index d73711bb..28e5ffbd 100644 --- a/.github/workflows/admu-ci.yml +++ b/.github/workflows/admu-ci.yml @@ -128,9 +128,10 @@ jobs: path: | ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.nuspec ${{ github.workspace }}/Jumpcloud-ADMU/Docs/*.md - ${{ github.workspace }}/Jumpcloud-ADMU/Exe/*.exe - ${{ github.workspace }}/Jumpcloud-ADMU/Powershell/Form.ps1 + ${{ github.workspace }}/Jumpcloud-ADMU/en-Us/JumpCloud.ADMU-help.xml + ${{ github.workspace }}/Jumpcloud-ADMU/PowerShell/**/*.ps1 ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.psd1 + ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.psm1 retention-days: 1 Test-Module: needs: ["Setup-Build-Dependancies", "Check-PR-Labels", "Build-Module"] diff --git a/.github/workflows/admu-release.yml b/.github/workflows/admu-release.yml index 55153bf4..5b4b53bf 100644 --- a/.github/workflows/admu-release.yml +++ b/.github/workflows/admu-release.yml @@ -121,7 +121,7 @@ jobs: - name: Pack nuspec shell: pwsh run: | - # set psm1 to point to public/start-migartion + # set psm1 to point to public/start-migration $psm1Path = "${{ github.workspace }}/jumpcloud-ADMU/JumpCloud.ADMU.psm1" $psm1Content = Get-Content -path $psm1Path $psm1Content = $psm1Content -replace "\\Powershell\\", "\Public\" diff --git a/Deploy/BuildNuspecFromPsd1.ps1 b/Deploy/BuildNuspecFromPsd1.ps1 index 34c69706..92231342 100644 --- a/Deploy/BuildNuspecFromPsd1.ps1 +++ b/Deploy/BuildNuspecFromPsd1.ps1 @@ -20,8 +20,8 @@ If (-not $ADMUGetConfig) { $nuspecFiles = @( @{src = ".\en-Us\JumpCloud.ADMU-help.xml"; target = "./" }, - @{src = ".\Powershell\Private\**\*.ps1"; target = "./Private" }, - @{src = ".\Powershell\Public\**\*.ps1"; target = "./Public" }, + @{src = ".\Powershell\Private\**\*.ps1"; target = "./Powershell/Private" }, + @{src = ".\Powershell\Public\**\*.ps1"; target = "./Powershell/Public" }, @{src = ".\Docs\*.md"; target = "./Docs" }, @{src = ".\JumpCloud.ADMU.psd1" }, @{src = ".\JumpCloud.ADMU.psm1" } diff --git a/jumpcloud-ADMU/JumpCloud.ADMU.psd1 b/jumpcloud-ADMU/JumpCloud.ADMU.psd1 index 60f105a7..08489855 100644 --- a/jumpcloud-ADMU/JumpCloud.ADMU.psd1 +++ b/jumpcloud-ADMU/JumpCloud.ADMU.psd1 @@ -18,7 +18,7 @@ ModuleVersion = '2.7.11' # CompatiblePSEditions = @() # ID used to uniquely identify this module -GUID = '1c69386e-055a-4e5b-801e-0a6ccf714d6e' +GUID = '192580de-87dc-4853-ab18-c4e59c5bb36e' # Author of this module Author = 'JumpCloud Customer Tools Team' From 472cd52af893483b988ffd27382567cd816ef9a2 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 14:42:10 -0700 Subject: [PATCH 17/73] functions tests --- .github/workflows/admu-ci.yml | 3 ++- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 11 +++++++++++ jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 | 11 +++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/admu-ci.yml b/.github/workflows/admu-ci.yml index 28e5ffbd..d8e0c828 100644 --- a/.github/workflows/admu-ci.yml +++ b/.github/workflows/admu-ci.yml @@ -129,7 +129,8 @@ jobs: ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.nuspec ${{ github.workspace }}/Jumpcloud-ADMU/Docs/*.md ${{ github.workspace }}/Jumpcloud-ADMU/en-Us/JumpCloud.ADMU-help.xml - ${{ github.workspace }}/Jumpcloud-ADMU/PowerShell/**/*.ps1 + ${{ github.workspace }}/Jumpcloud-ADMU/PowerShell/Private/**/*.ps1 + ${{ github.workspace }}/Jumpcloud-ADMU/PowerShell/Public/**/*.ps1 ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.psd1 ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.psm1 retention-days: 1 diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 95e31397..28926b55 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -18,9 +18,20 @@ BeforeAll { # import build variables for test cases write-host "Importing Build Variables:" . $PSScriptRoot\BuildVariables.ps1 + Write-Host "Running Connect-JCOnline" + Connect-JCOnline -JumpCloudApiKey $env:PESTER_APIKEY -JumpCloudOrgId $env:PESTER_ORGID -Force # import functions from start migration write-host "Importing Start-Migration Module:" Import-Module "$PSScriptRoot/../JumpCloud.ADMU.psd1" -Force + write-host "Importing private functions:" + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } # setup tests (This creates any of the users in the build vars dictionary) write-host "Running SetupAgent Script:" . $PSScriptRoot\SetupAgent.ps1 diff --git a/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 b/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 index 22b2dacc..51675a9a 100644 --- a/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 @@ -2,8 +2,15 @@ # Dot-source the variables for setupAgent/ migration tests: . $PSScriptRoot\BuildVariables.ps1 -# Dot-source start-migration -. $PSScriptRoot\..\Start-Migration.ps1 +# Dot-source private functions +$Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) +Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } +} # This helper function creates new local users and initializes their home directories # If the user exists and was created by the ADMU, the tool will attempt to remove the profile From fb4aa8d8c83b5f097d63bcaab8f959c8f519ea4a Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 14:49:44 -0700 Subject: [PATCH 18/73] update several variable names in test /ci --- .github/workflows/admu-ci.yml | 14 +++++++------- .github/workflows/admu-release.yml | 10 +++++----- Deploy/Get-Config.ps1 | 4 ++-- .../Powershell/Tests/Migration.Tests.ps1 | 4 ++-- jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/admu-ci.yml b/.github/workflows/admu-ci.yml index d8e0c828..8e2aa7b4 100644 --- a/.github/workflows/admu-ci.yml +++ b/.github/workflows/admu-ci.yml @@ -57,7 +57,7 @@ jobs: run: | # validate release type variables $env:RELEASE_TYPE | Should -BeIn @('major','minor','patch','manual') - Setup-Build-Dependancies: + Setup-Build-Dependencies: needs: ["Filter-Branch", "Check-PR-Labels", "Validate-Env-Variables"] runs-on: windows-latest timeout-minutes: 10 @@ -68,7 +68,7 @@ jobs: uses: actions/cache@v4 with: path: 'C:\Users\runneradmin\Documents\PowerShell\Modules\' - key: PS-Dependancies + key: PS-Dependencies - name: Install dependencies if: steps.cacher.outputs.cache-hit != 'true' shell: pwsh @@ -79,7 +79,7 @@ jobs: Write-Host ('[status]Installing package provider NuGet'); Install-PackageProvider -Name:('NuGet') -Scope:('CurrentUser') -Force } - # define dependancies for this ci workflow: + # define dependencies for this ci workflow: $PSDependencies = @{ 'PowerShellGet' = @{Repository = 'PSGallery'; RequiredVersion = '3.0.12-beta' } 'ps2exe' = @{Repository = 'PSGallery'; RequiredVersion = '1.0.13' } @@ -106,7 +106,7 @@ jobs: } } Build-Module: - needs: ["Setup-Build-Dependancies", "Check-PR-Labels"] + needs: ["Setup-Build-Dependencies", "Check-PR-Labels"] runs-on: windows-latest timeout-minutes: 10 steps: @@ -114,7 +114,7 @@ jobs: - uses: actions/cache@v4 with: path: 'C:\Users\runneradmin\Documents\PowerShell\Modules\' - key: PS-Dependancies + key: PS-Dependencies - name: Build ADMU Module shell: powershell env: @@ -135,7 +135,7 @@ jobs: ${{ github.workspace }}/Jumpcloud-ADMU/JumpCloud.ADMU.psm1 retention-days: 1 Test-Module: - needs: ["Setup-Build-Dependancies", "Check-PR-Labels", "Build-Module"] + needs: ["Setup-Build-Dependencies", "Check-PR-Labels", "Build-Module"] runs-on: windows-latest timeout-minutes: 75 strategy: @@ -151,7 +151,7 @@ jobs: - uses: actions/cache@v4 with: path: 'C:\Users\runneradmin\Documents\PowerShell\Modules\' - key: PS-Dependancies + key: PS-Dependencies - name: Test PWSH Module shell: powershell env: diff --git a/.github/workflows/admu-release.yml b/.github/workflows/admu-release.yml index 5b4b53bf..f367303f 100644 --- a/.github/workflows/admu-release.yml +++ b/.github/workflows/admu-release.yml @@ -54,7 +54,7 @@ jobs: } env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - Setup-Build-Dependancies: + Setup-Build-Dependencies: needs: ["Filter-Branch", "Check-PR-Labels"] runs-on: windows-latest timeout-minutes: 10 @@ -65,7 +65,7 @@ jobs: uses: actions/cache@v4 with: path: 'C:\Users\runneradmin\Documents\PowerShell\Modules\' - key: PS-Dependancies + key: PS-Dependencies - name: Install dependencies if: steps.cacher.outputs.cache-hit != 'true' shell: pwsh @@ -76,7 +76,7 @@ jobs: Write-Host ('[status]Installing package provider NuGet'); Install-PackageProvider -Name:('NuGet') -Scope:('CurrentUser') -Force } - # define dependancies for this ci workflow: + # define dependencies for this ci workflow: $PSDependencies = @{ 'PowerShellGet' = @{Repository = 'PSGallery'; RequiredVersion = '3.0.12-beta' } 'ps2exe' = @{Repository = 'PSGallery'; RequiredVersion = '1.0.13' } @@ -104,14 +104,14 @@ jobs: } Build-Sign-ADMU: runs-on: windows-latest - needs: ["Setup-Build-Dependancies", "Check-PR-Labels"] + needs: ["Setup-Build-Dependencies", "Check-PR-Labels"] environment: Production steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: "/home/runner/.local/share/powershell/Modules/" - key: PS-Dependancies + key: PS-Dependencies - name: Build ADMU Module shell: powershell env: diff --git a/Deploy/Get-Config.ps1 b/Deploy/Get-Config.ps1 index 5be2f253..e74803bb 100644 --- a/Deploy/Get-Config.ps1 +++ b/Deploy/Get-Config.ps1 @@ -11,8 +11,8 @@ Write-Host "======= Begin Get-Config =======" $env:ModuleVersionType = $ModuleVersionType $env:MODULENAME = $ModuleName # Populate variables -$ModuleFolderName = "$PSScriptroot/../JumpCloud-ADMU/" -$DEPLOYFOLDER = "$PSScriptroot" +$ModuleFolderName = "$PSScriptRoot/../JumpCloud-ADMU/" +$DEPLOYFOLDER = "$PSScriptRoot" $RELEASETYPE = $ModuleVersionType $GitHubWikiUrl = 'https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/' $ScriptRoot = Switch ($env:DEPLOYFOLDER) { diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 28926b55..95f80405 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -127,7 +127,7 @@ Describe 'Migration Test Scenarios' { # check that the FTA/PTA lists contain the $fileType and $protocol variable from the job Context 'Start-Migration on local accounts (Test Functionallity)' { - It "username extists for testing" { + It "username exists for testing" { foreach ($user in $userTestingHash.Values) { $user.username | Should -Not -BeNullOrEmpty $user.JCusername | Should -Not -BeNullOrEmpty @@ -176,7 +176,7 @@ Describe 'Migration Test Scenarios' { It "Test UWP_JCADMU was downloaded & exists" { Test-Path "C:\Windows\uwp_jcadmu.exe" | Should -Be $true } - It "Account of a prior migration can be sucessfully migrated again and not overwrite registry backup files" { + It "Account of a prior migration can be successfully migrated again and not overwrite registry backup files" { $Password = "Temp123!" $user1 = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) $user2 = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) diff --git a/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 b/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 index 51675a9a..52cf9f3d 100644 --- a/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/SetupAgent.ps1 @@ -40,7 +40,7 @@ Function InitUser { New-LocalUserProfile -username "$($UserName)" -ErrorVariable profileInit if ($profileInit) { Write-Log -Message:("$profileInit") - Write-Log -Message:("The user: $($UserName) could not be initalized, exiting") + Write-Log -Message:("The user: $($UserName) could not be initialized, exiting") exit 1 } } From 1af77a57ad68e1f611d45d360d6c612e91a91900 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 14:52:09 -0700 Subject: [PATCH 19/73] paths for tests --- Deploy/TestSetup.ps1 | 17 ++++++++++++++++- .../Powershell/Tests/Migration.Tests.ps1 | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Deploy/TestSetup.ps1 b/Deploy/TestSetup.ps1 index a2486fca..96b1b93e 100644 --- a/Deploy/TestSetup.ps1 +++ b/Deploy/TestSetup.ps1 @@ -11,7 +11,22 @@ process { #} # Load functions - . $PSScriptRoot\..\jumpcloud-ADMU\Powershell\Start-Migration.ps1 + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Public/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } #USMT & VC Variables $jcAdmuTempPath = 'C:\Windows\Temp\JCADMU\' diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 95f80405..6e01feaa 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -22,7 +22,7 @@ BeforeAll { Connect-JCOnline -JumpCloudApiKey $env:PESTER_APIKEY -JumpCloudOrgId $env:PESTER_ORGID -Force # import functions from start migration write-host "Importing Start-Migration Module:" - Import-Module "$PSScriptRoot/../JumpCloud.ADMU.psd1" -Force + Import-Module "$PSScriptRoot/../../JumpCloud.ADMU.psd1" -Force write-host "Importing private functions:" $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) Foreach ($Import in $Private) { From f97c47d0d45e4296e38c5ef0192fb8e0fad16ea1 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 15:22:49 -0700 Subject: [PATCH 20/73] update Build tests --- .../Powershell/Tests/Build.Tests.ps1 | 90 +++++++++++-------- .../Powershell/Tests/Functions.Tests.ps1 | 4 +- .../Powershell/Tests/Migration.Tests.ps1 | 2 +- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Build.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Build.Tests.ps1 index eb103441..2d138066 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Build.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Build.Tests.ps1 @@ -6,56 +6,74 @@ Describe "Module Validation Tests" { $env:ModuleVersionType = "build" } # Get Latest Module Version - $lastestModule = Find-Module -Name JumpCloud.ADMU + $latestModule = Find-Module -Name JumpCloud.ADMU + # Import Private Functions: + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } + # Import Public Functions: + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Public/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } } Context 'Check Versioning & Signature' { # Validate ProgressForm.ps1 ADMU version skip this test It 'Progress Form Version' { - $ProgressFormPath = "$PSScriptRoot\..\ProgressForm.ps1" $VersionRegex = [regex]'(?<=Title="JumpCloud ADMU )([0-9]+)\.([0-9]+)\.([0-9]+)' - $progressformversion = Select-String -Path:($ProgressFormPath) -Pattern:($VersionRegex) - $branchprogressformversion = [version]$progressformversion.Matches.value - $masterProgressForm = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/ProgressForm.ps1 -useBasicParsing).tostring() - $masterVersion = Select-String -inputobject:($masterProgressForm) -Pattern:($VersionRegex) - $masterprogressformversion = [version]$masterversion.Matches.value - if($env:ModuleVersionType -eq "manual"){ + $progressFormCmd = Get-Command New-ProgressForm + $progressFormVersion = $progressFormCmd.Definition | Select-String -Pattern:($VersionRegex) + $branchProgressFormVersion = [version]$progressFormVersion.Matches.value + $masterProgressForm = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/ProgressForm.ps1 -useBasicParsing).ToString() + $masterVersion = Select-String -InputObject:($masterProgressForm) -Pattern:($VersionRegex) + $masterProgressFormVersion = [version]$masterVersion.Matches.value + if ($env:ModuleVersionType -eq "manual") { # Manual Versioning # Given version should be greater than master - $branchprogressformversion | Should -BeGreaterThan $masterprogressformversion + $branchProgressFormVersion | Should -BeGreaterThan $masterProgressFormVersion } else { - $branchprogressformversion | Should -BeGreaterThan $masterprogressformversion - $branchprogressformversion.$($env:ModuleVersionType) | Should -Be ($masterprogressformversion.$($env:ModuleVersionType) + 1) + $branchProgressFormVersion | Should -BeGreaterThan $masterProgressFormVersion + $branchProgressFormVersion.$($env:ModuleVersionType) | Should -Be ($masterProgressFormVersion.$($env:ModuleVersionType) + 1) } - } -Skip + } It 'XAML Form version' { - $FormPath = "$PSScriptRoot\..\Form.ps1" $VersionRegex = [regex]'(?<=Title="JumpCloud ADMU )([0-9]+)\.([0-9]+)\.([0-9]+)' - $formversion = Select-String -Path:($formpath) -Pattern:($VersionRegex) - $branchformversion = [version]$formversion.Matches.value - $masterform = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/Form.ps1 -useBasicParsing).tostring() - $masterVersion = Select-String -inputobject:($masterform) -Pattern:($VersionRegex) - $masterformversion = [version]$masterversion.Matches.value - if($env:ModuleVersionType -eq "manual"){ + $formCmd = Get-Command Show-SelectionForm + $formVersion = $formCmd.Definition | Select-String -Pattern:($VersionRegex) + $branchFormVersion = [version]$formVersion.Matches.value + $masterForm = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/Form.ps1 -useBasicParsing).ToString() + $masterVersion = Select-String -InputObject:($masterForm) -Pattern:($VersionRegex) + $masterFormVersion = [version]$masterVersion.Matches.value + if ($env:ModuleVersionType -eq "manual") { # Manual Versioning # Given version should be greater than master - $branchformversion | Should -BeGreaterThan $masterformversion + $branchFormVersion | Should -BeGreaterThan $masterFormVersion } else { - $branchformversion | Should -BeGreaterThan $masterformversion - $branchformversion.$($env:ModuleVersionType) | Should -Be ($masterformversion.$($env:ModuleVersionType) + 1) + $branchFormVersion | Should -BeGreaterThan $masterFormVersion + $branchFormVersion.$($env:ModuleVersionType) | Should -Be ($masterFormVersion.$($env:ModuleVersionType) + 1) } } It 'Start-Migration version' { - $startMigrationPath = "$PSScriptRoot\..\Start-Migration.ps1" $VersionRegex = [regex]"(?<=admuVersion = ')(([0-9]+)\.([0-9]+)\.([0-9]+))" - $admuversion = Select-String -Path:($startMigrationPath) -Pattern:($VersionRegex) - $branchStartMigrationVersion = [version]$admuversion.Matches.value - $masterStartMigration = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/Start-Migration.ps1 -useBasicParsing).tostring() - $masterVersion = Select-String -inputobject:($masterStartMigration) -Pattern:($VersionRegex) + $startMigrationCmd = Get-Command Start-Migration + $admuVersion = $startMigrationCmd.Definition | Select-String -Pattern:($VersionRegex) + $branchStartMigrationVersion = [version]$admuVersion.Matches.value + $masterStartMigration = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/Start-Migration.ps1 -useBasicParsing).ToString() + $masterVersion = Select-String -InputObject:($masterStartMigration) -Pattern:($VersionRegex) $masterStartMigrationVersion = [version]$masterVersion.Matches.value if ($env:ModuleVersionType -eq "manual") { $branchStartMigrationVersion | Should -BeGreaterThan $masterStartMigrationVersion @@ -67,15 +85,15 @@ Describe "Module Validation Tests" { It 'gui_jcadmu.exe version' -skip { $VersionRegex = [regex]'(?<=Title="JumpCloud ADMU )([0-9]+)\.([0-9]+)\.([0-9]+)' - $masterform = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/Form.ps1 -useBasicParsing).tostring() - $masterVersion = Select-String -inputobject:($masterform) -Pattern:($VersionRegex) - $masterformversion = [version]$masterversion.Matches.value - $exeversion = [version](Get-Item ("$PSScriptRoot\..\..\exe\gui_jcadmu.exe")).VersionInfo.FileVersion + $masterForm = (Invoke-WebRequest https://raw.githubusercontent.com/TheJumpCloud/jumpcloud-ADMU/master/jumpcloud-ADMU/Powershell/Form.ps1 -useBasicParsing).ToString() + $masterVersion = Select-String -InputObject:($masterForm) -Pattern:($VersionRegex) + $masterFormVersion = [version]$masterVersion.Matches.value + $exeVersion = [version](Get-Item ("$PSScriptRoot\..\..\exe\gui_jcadmu.exe")).VersionInfo.FileVersion if ($env:ModuleVersionType -eq "manual") { - $exeversion | Should -BeGreaterThan $masterformversion + $exeVersion | Should -BeGreaterThan $masterFormVersion } else { - $exeversion | Should -BeGreaterThan $masterformversion - $exeversion.$($env:ModuleVersionType) | Should -Be ($masterformversion.$($env:ModuleVersionType) + 1) + $exeVersion | Should -BeGreaterThan $masterFormVersion + $exeVersion.$($env:ModuleVersionType) | Should -Be ($masterFormVersion.$($env:ModuleVersionType) + 1) } } } @@ -87,13 +105,13 @@ Describe "Module Validation Tests" { $ModuleChangelogContent = Get-Content -Path:($FilePath_ModuleChangelog) } - It 'Module Changlog Version should be correct' { + It 'Module ChangLog Version should be correct' { $ModuleChangelogVersionRegex = "([0-9]+)\.([0-9]+)\.([0-9]+)" $ModuleChangelogVersionMatch = ($ModuleChangelogContent | Select-Object -First 1) | Select-String -Pattern:($ModuleChangelogVersionRegex) Write-Host "Module Changelog Content: $ModuleChangelogVersionMatch" $ModuleChangelogVersion = $ModuleChangelogVersionMatch.Matches.Value Write-Host "Module Changelog Version: $ModuleChangelogVersion" - $latestVersion = [version]$lastestModule.version + $latestVersion = [version]$latestModule.version if ($env:ModuleVersionType -eq "manual") { $ModuleChangelogVersion | Should -BeGreaterThan $latestVersion } else { diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index 8c678d7e..8dbf4f75 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -111,7 +111,7 @@ Describe 'Functions' { $Password = "Temp123!" $user1 = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) # If User Exists, remove from the org - $users = Get-JCSDKUser + $users = Get-JCSdkUser if ("$($user.JCUsername)" -in $users.Username) { $existing = $users | Where-Object { $_.username -eq "$($user.JCUsername)" } Write-Host "Found JumpCloud User, $($existing.Id) removing..." @@ -187,7 +187,7 @@ Describe 'Functions' { } It 'User does not exist on system and throws exception' { - { New-LocalUserProfile -username:('userdoesntexist') -ErrorAction Stop } | Should -Throw + { New-LocalUserProfile -username:('UserDoesNotExist') -ErrorAction Stop } | Should -Throw } } diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 6e01feaa..b2a52f78 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -539,7 +539,7 @@ Describe 'Migration Test Scenarios' { } AfterAll { - $systems = Get-JCsdkSystem + $systems = Get-JCSdkSystem $CIsystems = $systems | Where-Object { $_.displayname -match "fv-az*" } foreach ($system in $CIsystems) { Remove-JcSdkSystem -id $system.Id From 442a021d48c083f0a740fad29f178ee8569b9559 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 15:22:55 -0700 Subject: [PATCH 21/73] Update VS Settings --- .vscode/settings.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bd7c6572..0b345666 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,9 @@ "powershell.scriptAnalysis.settingsPath": "jumpcloud-ADMU/Powershell/Tests/PSScriptAnalyzerSettings.psd1", "powershell.codeFormatting.preset": "OTBS", "powershell.scriptAnalysis.enable": true, - "files.trimTrailingWhitespace": true + "files.trimTrailingWhitespace": true, + "search.useIgnoreFiles": false, + "cSpell.words": [ + "ADMU", + ] } From 471b218e37e7184ccb20454ea720a7acfb93c3fd Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 15:26:47 -0700 Subject: [PATCH 22/73] paths for tests --- Deploy/TestSetup.ps1 | 4 ++-- .../Powershell/Tests/Functions.Tests.ps1 | 3 +-- .../Tests/ScheduledTaskTest.Tests.ps1 | 21 ++++++++++++++--- .../Tests/SetLastLoggedOnUserTest.Tests.ps1 | 23 +++++++++++++++---- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Deploy/TestSetup.ps1 b/Deploy/TestSetup.ps1 index 96b1b93e..c5ab46f1 100644 --- a/Deploy/TestSetup.ps1 +++ b/Deploy/TestSetup.ps1 @@ -11,7 +11,7 @@ process { #} # Load functions - $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Private/*.ps1" -Recurse) Foreach ($Import in $Private) { Try { . $Import.FullName @@ -19,7 +19,7 @@ process { Write-Error -Message "Failed to import function $($Import.FullName): $_" } } - $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Public/*.ps1" -Recurse) + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../jumpcloud-ADMU/Powershell/Public/*.ps1" -Recurse) Foreach ($Import in $Private) { Try { . $Import.FullName diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index 8dbf4f75..a5570466 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -11,8 +11,7 @@ BeforeAll { Function Get-WindowsDrive { return 'drive' } - # TODO: import private functions: - Write-Host "Dot-Sourcing Private Functions" + # Import Private Functions: $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) Foreach ($Import in $Private) { Try { diff --git a/jumpcloud-ADMU/Powershell/Tests/ScheduledTaskTest.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/ScheduledTaskTest.Tests.ps1 index 1f7ec0e8..90a53333 100644 --- a/jumpcloud-ADMU/Powershell/Tests/ScheduledTaskTest.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/ScheduledTaskTest.Tests.ps1 @@ -16,9 +16,24 @@ BeforeAll { # import build variables for test cases write-host "Importing Build Variables:" . $PSScriptRoot\BuildVariables.ps1 - # import functions from start migration - write-host "Importing Start-Migration Script:" - . $PSScriptRoot\..\Start-Migration.ps1 + # Import Private Functions: + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } + # Import Public Functions: + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Public/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } # setup tests (This creates any of the users in the build vars dictionary) write-host "Running SetupAgent Script:" . $PSScriptRoot\SetupAgent.ps1 diff --git a/jumpcloud-ADMU/Powershell/Tests/SetLastLoggedOnUserTest.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/SetLastLoggedOnUserTest.Tests.ps1 index 72f8b798..d3a14eb3 100644 --- a/jumpcloud-ADMU/Powershell/Tests/SetLastLoggedOnUserTest.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/SetLastLoggedOnUserTest.Tests.ps1 @@ -16,14 +16,29 @@ BeforeAll { # import build variables for test cases write-host "Importing Build Variables:" . $PSScriptRoot\BuildVariables.ps1 - # import functions from start migration - write-host "Importing Start-Migration Script:" - . $PSScriptRoot\..\Start-Migration.ps1 + # Import Private Functions: + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Private/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } + # Import Public Functions: + $Private = @( Get-ChildItem -Path "$PSScriptRoot/../Public/*.ps1" -Recurse) + Foreach ($Import in $Private) { + Try { + . $Import.FullName + } Catch { + Write-Error -Message "Failed to import function $($Import.FullName): $_" + } + } # setup tests (This creates any of the users in the build vars dictionary) write-host "Running SetupAgent Script:" . $PSScriptRoot\SetupAgent.ps1 } -Describe 'Set-LastLoggedOnUser Test Scenarios'{ +Describe 'Set-LastLoggedOnUser Test Scenarios' { Enable-TestNameAsVariablePlugin BeforeEach { Write-Host "---------------------------" From 7a4347493eb12d3d376349520b32fa48a9635619 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 13 Jan 2025 16:02:39 -0700 Subject: [PATCH 23/73] Get-SID replace --- jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 b/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 index ba3ea21e..a4633e3c 100644 --- a/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 @@ -12,7 +12,7 @@ function Remove-LocalUserProfile { foreach ($user in $users) { # we only want to remove users with description "Created By JumpCloud ADMU" if ( $user.name -match $UserName -And $user.description -eq "Created By JumpCloud ADMU" ) { - $UserSid = Get-SID -User $UserName + $UserSid = Get-SecurityIdentifier -User $UserName $UserPath = Get-ProfileImagePath -UserSid $UserSid # Set RemoveUser bool to true $removeUser = $true From cb9d51ff30dfcae187d408d459d5331b97b61729 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 11:11:43 -0700 Subject: [PATCH 24/73] migration tests paths --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index b2a52f78..a83c4f05 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -198,7 +198,7 @@ Describe 'Migration Test Scenarios' { (Get-ChildItem "C:\Users\$user1\AppData\Local\Microsoft\Windows\" -Hidden | Where-Object { $_.Name -match "UsrClass_original" }).Count | Should -Be 2 } - It "Start-Migration should throw if the jumpcloud user already exists & not migrate anything" { + It "Start-Migration should throw if the JumpCloud user already exists & not migrate anything" { $Password = "Temp123!" $user1 = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) $user2 = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) @@ -240,12 +240,12 @@ Describe 'Migration Test Scenarios' { # variables for test $CommandBody = ' try { - . "D:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Start-Migration.ps1" + . "D:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Public\Start-Migration.ps1" } catch { Write-Host "no file exists" } try { - . "C:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Start-Migration.ps1" + . "C:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Public\Start-Migration.ps1" } catch { Write-Host "no file exists" } @@ -380,7 +380,7 @@ Describe 'Migration Test Scenarios' { } } Context 'Start-Migration Fails when LocalUsername and JumpCloudUsername are the same' { - It 'local and jumpcloud usernames are the same' { + It 'local and JumpCloud usernames are the same' { Write-Host "`nStart-Migration Fails when LocalUsername and JumpCloudUsername are the same" $Password = "Temp123!" $user1 = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) @@ -390,7 +390,7 @@ Describe 'Migration Test Scenarios' { { Start-Migration -JumpCloudAPIKey $env:PESTER_APIKEY -JumpCloudUserName "testUser" -SelectedUserName "$ENV:COMPUTERNAME\testUser" -TempPassword "$($Password)" } | Should -Throw } } - Context 'Start-Migration on Local Accounts Expecting Failed Results (Test Reversal Functionallity)' { + Context 'Start-Migration on Local Accounts Expecting Failed Results (Test Reversal Functionality)' { BeforeEach { # Remove the log from previous runs # Not necessary but will be used in future tests to check log results From b95d193f3e40afb95464a71caa5361ffa57ccb21 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 13:45:52 -0700 Subject: [PATCH 25/73] migration tests paths --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index a83c4f05..e12574a0 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -240,12 +240,13 @@ Describe 'Migration Test Scenarios' { # variables for test $CommandBody = ' try { - . "D:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Public\Start-Migration.ps1" + # . "D:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Public\Start-Migration.ps1" + Import-Module "D:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\JumpCloud.ADMU.psd1" -Force } catch { Write-Host "no file exists" } try { - . "C:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\Powershell\Public\Start-Migration.ps1" + Import-Module "C:\a\jumpcloud-ADMU\jumpcloud-ADMU\jumpcloud-ADMU\JumpCloud.ADMU.psd1" -Force } catch { Write-Host "no file exists" } @@ -406,7 +407,7 @@ Describe 'Migration Test Scenarios' { foreach ($user in $JCReversionHash.Values) { # Begin background job before Start-Migration # define path to start migration for parallel job: - $pathToSM = "$PSScriptRoot\..\Start-Migration.ps1" + $pathToSM = "$PSScriptRoot\..\Public\Start-Migration.ps1" Write-Host "$(Get-Date -UFormat "%D %r") - Start parallel job to wait for new user directory" $waitJob = Start-Job -ScriptBlock:( { From 3eff0e138784aa4196cd2ba817f1a09c6f7e77c1 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 15:01:50 -0700 Subject: [PATCH 26/73] remove start-jcadmu.ps1 file --- .github/workflows/admu-release.yml | 9 ++--- Deploy/Build.ps1 | 2 +- Deploy/New-AdmuExe.ps1 | 6 +-- jumpcloud-ADMU/Powershell/Start-JCADMU.ps1 | 23 ------------ jumpcloud-ADMU/ReadMe.md | 43 +++++++--------------- 5 files changed, 21 insertions(+), 62 deletions(-) delete mode 100644 jumpcloud-ADMU/Powershell/Start-JCADMU.ps1 diff --git a/.github/workflows/admu-release.yml b/.github/workflows/admu-release.yml index f367303f..0658ac0a 100644 --- a/.github/workflows/admu-release.yml +++ b/.github/workflows/admu-release.yml @@ -121,13 +121,10 @@ jobs: - name: Pack nuspec shell: pwsh run: | - # set psm1 to point to public/start-migration - $psm1Path = "${{ github.workspace }}/jumpcloud-ADMU/JumpCloud.ADMU.psm1" - $psm1Content = Get-Content -path $psm1Path - $psm1Content = $psm1Content -replace "\\Powershell\\", "\Public\" - Set-Content -Value $psm1Content -Path $psm1Path -Force # Pack - nuget pack "${{ github.workspace }}/jumpcloud-ADMU/JumpCloud.ADMU.nuspec" + # NU5111 - ignore unrecognized ps1 files not named install/ uninstall or init + # NU5110 - ignore files outside tools folder + nuget pack "${{ github.workspace }}/jumpcloud-ADMU/JumpCloud.ADMU.nuspec" -Properties NoWarn=NU5111,NU5110 - name: Validate NuPkg File shell: pwsh run: | diff --git a/Deploy/Build.ps1 b/Deploy/Build.ps1 index 91b43120..336a2f8a 100644 --- a/Deploy/Build.ps1 +++ b/Deploy/Build.ps1 @@ -28,7 +28,7 @@ if ($ModuleVersionType -eq 'manual') { New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" # Run Build-Exe On Windows Systems if ($IsWindows) { - . $PSScriptRoot\Build-Exe.ps1 + . $PSScriptRoot\New-ADMUExe.ps1 } # Run Build-HelpFiles . $PSScriptRoot\Build-HelpFiles.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) diff --git a/Deploy/New-AdmuExe.ps1 b/Deploy/New-AdmuExe.ps1 index 540099b3..98b8f294 100644 --- a/Deploy/New-AdmuExe.ps1 +++ b/Deploy/New-AdmuExe.ps1 @@ -38,13 +38,13 @@ Write-Host "Psd1 Version: $PSD1Version" # Validate Version $FormVersion | Should -Be $PSD1Version -# Build ADMU.PS1 File: -. $PSScriptRoot\New-ADMUTemplate.ps1 -New-ADMUTemplate # Clear existing file If (Test-Path -Path:("$PSScriptRoot/admuTemplate.ps1")) { "A Template file exists" } else { + # Build ADMU.PS1 File: + . $PSScriptRoot\New-ADMUTemplate.ps1 + New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" throw "A template file does not exist, an EXE can not be built." } diff --git a/jumpcloud-ADMU/Powershell/Start-JCADMU.ps1 b/jumpcloud-ADMU/Powershell/Start-JCADMU.ps1 deleted file mode 100644 index 4a31ef10..00000000 --- a/jumpcloud-ADMU/Powershell/Start-JCADMU.ps1 +++ /dev/null @@ -1,23 +0,0 @@ - # Check runningaslocaladmin - if (([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")) -eq $false) { - Write-Host 'ADMU must be ran as a local administrator..please correct & try again' - Read-Host -Prompt "Press Enter to exit" - exit - } - - # Get script path - $scriptPath = (Split-Path -Path:($MyInvocation.MyCommand.Path)) - - # Load functions - . ($scriptPath + '\Start-Migration.ps1') - - # Load ProgressForm - . ($scriptPath + '\ProgressForm.ps1') - # Load form - $formResults = Invoke-Expression -Command:('. "' + $scriptPath + '\Form.ps1"') - # exit if form is null/ false - If ($formResults) { - Start-Migration -inputObject:($formResults) - } Else { - Write-Output ('Exiting ADMU process') - } diff --git a/jumpcloud-ADMU/ReadMe.md b/jumpcloud-ADMU/ReadMe.md index 3f14c904..4dc5dc6e 100644 --- a/jumpcloud-ADMU/ReadMe.md +++ b/jumpcloud-ADMU/ReadMe.md @@ -1,6 +1,6 @@ # jumpcloud-ADMU -Jumpcloud Active Directory Migration Utility. +JumpCloud Active Directory Migration Utility. ## Development @@ -11,15 +11,12 @@ This project is being developed in Powershell using XAML front-end. The directory `Deploy` contains a set of scripts used in the pipeline for CI and CD. - **admu.ico:** Icon file used in .exe generation -- **ADMU.ps1:** Combined single file output used in .exe generation -- **Build.ps1:** Builds .exe utilizing ps2exe module +- **Build.ps1:** Builds entire module and related help documentation - **Build-HelpFiles.ps1:** Uses PlatyPS to generate function help file +- **New-ADMUExe.ps1:** Builds a copy of all code files into a single file, Invokes PS2EXE to create an executable - **BuildNuspecFromPsd1.ps1:** Builds Nuspec from Powershell Module to publish - **Get-Config.ps1:** Used to set project configuration vars from CI pipeline -- **Invoke-GitCommit.ps1:** Git commit script used in pipeline -- **Sign.ps1:** Signs .exe with codesigning certificate - **TestSetup.ps1:** Ran in pipeline to clear & install latest JC agent using ADMU functions - - **Get-PSGalleryModuleVersion.ps1:** Used to increment and find currently published module version - **New-ModuleChangeLog.ps1:** Used to generate and add to module changelog md file @@ -27,23 +24,10 @@ The directory `Docs` contains the generated and populated help files for the Pow - **`Start-Migration.md`:** Help file for start-migration function -The directory `jumpcloud-ADMU\Exe\` contains the signed .exe output from the pipeline build used in the release steps. - -- **gui_jcadmu.exe:** Signed executable file - -The directory `jumpcloud-ADMU\Gpo\` contains the GPO's used in mass deployment/invoke-command scenario scripts. - The directory `jumpcloud-ADMU\Powershell\` contains the Powershell scripts used by the ADMU. -- **Form.ps1:** XAML & form logic -- **Functions.ps1:** Utilized functions in gui & Start-Migration Function -- **InvokePester.ps1:** Calls invoke-pester with formated output for pipeline -- **Start-JCADMU.ps1:** Calls Form.ps1 & passes output to Start-Migration Function as object - The directory `jumpcloud-ADMU\Powershell\Tests\` contains the signed .exe output from the pipeline build used in the release steps. -- **gui_jcadmu.exe:** Signed executable file - The directory `jumpcloud-ADMU-Advanced-Deployment` contains powershell scripts to run discovery and migration in a mass deployment scenario. - **invoke-admu-discovery.ps1:** Used to collect and output ADMU_DISCOVERY.csv which contains domain accounts and information from each system @@ -51,24 +35,20 @@ The directory `jumpcloud-ADMU-Advanced-Deployment` contains powershell scripts t ### CI\CD Pipeline -https://dev.azure.com/JumpCloudPowershell/JumpCloud%20ADMU/_build?definitionId=24&_a=summary - The pipeline runs the following steps on CI builds: - **Powershell Build Script:** Builds exe from powershell scripts -- **Powershell Sign exe:** Signs exe build with code signing certificate -- **Test Setup Script:** Setup build server with domain joined agent system -- **InvokePester Script:** Runs pester tests & verifys execuatable signature +- **InvokePester Script:** Runs pester tests & verify executable signature - **Copy Files to:powershell:** Copy powershell files to artifact directory for use on release - **Copy Files to:exe:** Copy exe to artifact directory for use on release - **Publish Artifact: ADMU:** Publish artifact directory to pipeline artifact -- **Invoke-GitCommit - BranchName:** Commit execuatble build back to branch if previous steps pass without issue +- **Invoke-GitCommit - BranchName:** Commit executable build back to branch if previous steps pass without issue The pipeline runs the following steps on CD releases: -- **_JumpCloudADMU-CI artifact:** powershell & exe files from successful build branch +- **\_JumpCloudADMU-CI artifact:** powershell & exe files from successful build branch - **GH Release (create):** GitHub release draft is created containing artifact assets - - Update Tag & Release notes + - Update Tag & Release notes ### Testing @@ -84,8 +64,13 @@ Build.Tests.ps1 - Checks built exe build number Functions.Tests.ps1 -gpo.Tests.ps1 + +- Unit tests for the functions used in `Start-Migration` + +Migration.Tests.ps1 + +- Tests the functionality of the `Start-Migration` function PSScriptAnalyzer.Tests.ps1 -- Runs psscriptanalyzer against powershell directory with custom exclude rules. \ No newline at end of file +- Runs PSScriptAnalyzer on powershell directory with custom exclude rules. From fa4de49c3ab1fcf6f9eaaaa952847466c7472aad Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 15:10:57 -0700 Subject: [PATCH 27/73] escape $pid --- Deploy/Functions/New-ADMUTemplate.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Deploy/Functions/New-ADMUTemplate.ps1 b/Deploy/Functions/New-ADMUTemplate.ps1 index 56f49d4f..08c9e4a4 100644 --- a/Deploy/Functions/New-ADMUTemplate.ps1 +++ b/Deploy/Functions/New-ADMUTemplate.ps1 @@ -81,7 +81,7 @@ $PrivateFunctionsContent `"@ -Name "Win32ShowWindowAsync" -Namespace "Win32Functions" -PassThru # PID of the current process # Get PID of the current process -`$FormWindowPIDHandle = (Get-Process -Id $pid).MainWindowHandle +`$FormWindowPIDHandle = (Get-Process -Id `$pid).MainWindowHandle `$ShowWindowAsync::ShowWindowAsync(`$FormWindowPIDHandle, 0) | Out-Null # PID "@ From 44a9393318a74baec4bf75d08df1ee01ef4a1dda Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 19:31:49 -0700 Subject: [PATCH 28/73] write out log content --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index e12574a0..448b5b39 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -496,7 +496,9 @@ Describe 'Migration Test Scenarios' { } Catch { write-host "Migration failed as expected" } - $logContent = Get-Content -Tail 1 C:\Windows\Temp\Jcadmu.log + $logContent = Get-Content -Tail 10 C:\Windows\Temp\Jcadmu.log + Write-Host "last 10 lines of log:" + Write-Host $logContent if ($logContent -match "The following migration steps were reverted to their original state: newUserInit") { write-host "Start Migration Task Failed Successfully" return $true From 7a0a8e824afd31b740552275272fa5d835de64e1 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 20:11:40 -0700 Subject: [PATCH 29/73] skip tests and validate log location in build runner --- .../Powershell/Tests/Migration.Tests.ps1 | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 448b5b39..bfd67eaa 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -63,7 +63,7 @@ Describe 'Migration Test Scenarios' { Write-Host "---------------------------" Write-Host "Begin Test: $testName`n" } - Context 'Test FTA/PTA CSV Creation' { + Context 'Test FTA/PTA CSV Creation' -skip { It 'Creates FTA/PTA CSV files and changes file/protocol associations' { $Password = "Temp123!" $localUser = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) @@ -126,7 +126,7 @@ Describe 'Migration Test Scenarios' { # check that the FTA/PTA lists contain the $fileType and $protocol variable from the job - Context 'Start-Migration on local accounts (Test Functionallity)' { + Context 'Start-Migration on local accounts (Test Functionallity)' -skip { It "username exists for testing" { foreach ($user in $userTestingHash.Values) { $user.username | Should -Not -BeNullOrEmpty @@ -225,7 +225,7 @@ Describe 'Migration Test Scenarios' { } } - Context 'Start-Migration kicked off through JumpCloud agent' { + Context 'Start-Migration kicked off through JumpCloud agent' -skip { BeforeAll { # test connection to Org $Org = Get-JcSdkOrganization @@ -322,7 +322,7 @@ Describe 'Migration Test Scenarios' { } } } - Context 'Start-Migration Successfully Binds JumpCloud User to System' { + Context 'Start-Migration Successfully Binds JumpCloud User to System' -skip { It 'user bound to system after migration' { $headers = @{} $headers.Add("x-org-id", $env:PESTER_ORGID) @@ -369,7 +369,7 @@ Describe 'Migration Test Scenarios' { } } } - Context 'Start-Migration Fails to Bind JumpCloud User to System and throws error' { + Context 'Start-Migration Fails to Bind JumpCloud User to System and throws error' -skip { It 'user bound to system after migration' { Write-Host "`nBegin Test: Start-Migration Fails to Bind JumpCloud User to System and throws error" $Password = "Temp123!" @@ -380,7 +380,7 @@ Describe 'Migration Test Scenarios' { { Start-Migration -JumpCloudAPIKey $env:PESTER_APIKEY -AutobindJCUser $true -JumpCloudUserName "$($user2)" -SelectedUserName "$ENV:COMPUTERNAME\$($user1)" -TempPassword "$($Password)" } | Should -Throw } } - Context 'Start-Migration Fails when LocalUsername and JumpCloudUsername are the same' { + Context 'Start-Migration Fails when LocalUsername and JumpCloudUsername are the same' -skip { It 'local and JumpCloud usernames are the same' { Write-Host "`nStart-Migration Fails when LocalUsername and JumpCloudUsername are the same" $Password = "Temp123!" @@ -496,7 +496,14 @@ Describe 'Migration Test Scenarios' { } Catch { write-host "Migration failed as expected" } - $logContent = Get-Content -Tail 10 C:\Windows\Temp\Jcadmu.log + if (Test-Path "C:\Windows\Temp\Jcadmu.log") { + Write-Host "log located in c drive" + $logContent = Get-Content -Tail 10 C:\Windows\Temp\Jcadmu.log + } + if (Test-Path "D:\Windows\Temp\Jcadmu.log") { + Write-Host "log located in D drive" + $logContent = Get-Content -Tail 10 D:\Windows\Temp\Jcadmu.log + } Write-Host "last 10 lines of log:" Write-Host $logContent if ($logContent -match "The following migration steps were reverted to their original state: newUserInit") { From 0badb2876344f87aafcad3e56a9688a071adc3be Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Tue, 14 Jan 2025 20:33:01 -0700 Subject: [PATCH 30/73] not tail log lines --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index bfd67eaa..28267642 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -498,13 +498,9 @@ Describe 'Migration Test Scenarios' { } if (Test-Path "C:\Windows\Temp\Jcadmu.log") { Write-Host "log located in c drive" - $logContent = Get-Content -Tail 10 C:\Windows\Temp\Jcadmu.log + $logContent = Get-Content C:\Windows\Temp\Jcadmu.log } - if (Test-Path "D:\Windows\Temp\Jcadmu.log") { - Write-Host "log located in D drive" - $logContent = Get-Content -Tail 10 D:\Windows\Temp\Jcadmu.log - } - Write-Host "last 10 lines of log:" + Write-Host "last lines of log:" Write-Host $logContent if ($logContent -match "The following migration steps were reverted to their original state: newUserInit") { write-host "Start Migration Task Failed Successfully" From ad25854be670c3d542ccb3696ef58696a22201ee Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 15 Jan 2025 08:14:40 -0700 Subject: [PATCH 31/73] Write-ToLog shouldn't terminate when writing an error --- jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 index bfc0aa58..5f2cf548 100644 --- a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 @@ -76,7 +76,7 @@ Function Write-ToLog { } else { Switch ($Level) { 'Error' { - Write-Error $Message + # Write-Error $Message $LevelText = 'ERROR:' } 'Warn' { From 2de84e175dd680113e0b7f62168509ffe3b4ff95 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 15 Jan 2025 08:55:23 -0700 Subject: [PATCH 32/73] order in error messages --- jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 | 2 +- jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 | 2 +- jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 b/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 index a4633e3c..c0fac048 100644 --- a/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Remove-LocalUserProfile.ps1 @@ -47,7 +47,7 @@ function Remove-LocalUserProfile { # Remove the User SID # TODO: if the profile SID is loaded in registry skip this and note in log # Match the user SID - $matchedKey = get-childitem -path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' | Where-Object { $_.Name -match $UserSid } + $matchedKey = Get-ChildItem -path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' | Where-Object { $_.Name -match $UserSid } # Set the Matched Key Path to PSPath so PowerShell can use the path $matchedKeyPath = $($matchedKey.Name) -replace "HKEY_LOCAL_MACHINE", "HKLM:" # Remove the UserSid Key from the ProfileList diff --git a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 index 5f2cf548..bfc0aa58 100644 --- a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 @@ -76,7 +76,7 @@ Function Write-ToLog { } else { Switch ($Level) { 'Error' { - # Write-Error $Message + Write-Error $Message $LevelText = 'ERROR:' } 'Warn' { diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index 1d78133a..ad260f56 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -1137,8 +1137,8 @@ Function Start-Migration { } else { Write-ToLog -Message:("ADMU encoutered the following errors: $($admuTracker.Keys | Where-Object { $admuTracker[$_].fail -eq $true })") -Level Warn - Write-ToLog -Message:('Script finished with errors; Log file location: ' + $jcAdmuLogFile) -Level Error Write-ToLog -Message:("The following migration steps were reverted to their original state: $FixedErrors") -Level Warn + Write-ToLog -Message:('Script finished with errors; Log file location: ' + $jcAdmuLogFile) -Level Error Write-ToProgress -ProgressBar $Progressbar -Status $Script:ErrorMessage -form $isForm -logLevel "Error" throw "JumpCloud ADMU was unable to migrate $selectedUserName" } From 796f4de6bccbbf21f98a11176110a4bd9458f283 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 15 Jan 2025 10:04:55 -0700 Subject: [PATCH 33/73] skip functions tests + why are we using silentlyContinue? --- jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 | 2 +- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index a5570466..55faeac0 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -21,7 +21,7 @@ BeforeAll { } } } -Describe 'Functions' { +Describe 'Functions' -skip { BeforeAll { Mock Get-WindowsDrive { return "C:" } } diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 28267642..0e43b679 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -492,7 +492,7 @@ Describe 'Migration Test Scenarios' { } Write-Host "Running Migration, $JCUSERNAME, $SELECTEDCOMPUTERNAME, $TEMPPASS" try { - Start-Migration -AutobindJCUser $false -JumpCloudUserName "$($JCUSERNAME)" -SelectedUserName "$ENV:COMPUTERNAME\$($SELECTEDCOMPUTERNAME)" -TempPassword "$($TEMPPASS)" -ErrorAction SilentlyContinue | Out-Null + Start-Migration -AutobindJCUser $false -JumpCloudUserName "$($JCUSERNAME)" -SelectedUserName "$ENV:COMPUTERNAME\$($SELECTEDCOMPUTERNAME)" -TempPassword "$($TEMPPASS)" | Out-Null } Catch { write-host "Migration failed as expected" } From 07c01ab7ef176c9de321d92a1194bb6dc3c78f77 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 15 Jan 2025 10:16:59 -0700 Subject: [PATCH 34/73] can't skip functions tests --- jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index 55faeac0..a5570466 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -21,7 +21,7 @@ BeforeAll { } } } -Describe 'Functions' -skip { +Describe 'Functions' { BeforeAll { Mock Get-WindowsDrive { return "C:" } } From 4c981228c2c5cda5bfff36902275b61c3e578220 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Wed, 15 Jan 2025 15:53:01 -0700 Subject: [PATCH 35/73] write-error should be write-warnings until final throw message --- .../Powershell/Private/Get-NetBiosName.ps1 | 24 ++-------------- .../Private/Write-AdmuErrorMessage.ps1 | 18 ++++++------ .../Powershell/Private/Write-ToLog.ps1 | 3 +- .../Powershell/Public/Start-Migration.ps1 | 28 +++++++++---------- .../Powershell/Tests/Functions.Tests.ps1 | 7 ----- .../Powershell/Tests/Migration.Tests.ps1 | 2 +- 6 files changed, 28 insertions(+), 54 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 index 0f8bdb1d..166a8807 100644 --- a/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Get-NetBiosName.ps1 @@ -1,24 +1,4 @@ -#TODO Add check if library installed on system, else don't import -Add-Type -MemberDefinition @" -[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] -public static extern uint NetApiBufferFree(IntPtr Buffer); -[DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] -public static extern int NetGetJoinInformation( - string server, - out IntPtr NameBuffer, - out int BufferType); -"@ -Namespace Win32Api -Name NetApi32 - function Get-NetBiosName { - $pNameBuffer = [IntPtr]::Zero - $joinStatus = 0 - $apiResult = [Win32Api.NetApi32]::NetGetJoinInformation( - $null, # lpServer - [Ref] $pNameBuffer, # lpNameBuffer - [Ref] $joinStatus # BufferType - ) - if ( $apiResult -eq 0 ) { - [Runtime.InteropServices.Marshal]::PtrToStringAuto($pNameBuffer) - [Void] [Win32Api.NetApi32]::NetApiBufferFree($pNameBuffer) - } + $domain = (Get-WmiObject -Class Win32_ComputerSystem).Domain + return $domain } \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 index 77c691a1..481e780f 100644 --- a/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 @@ -4,43 +4,43 @@ function Write-AdmuErrorMessage { ) switch ($ErrorName) { "load_unload_error" { - Write-ToLog -Message "Load/Unload Error: The user registry cannot be loaded or unloaded. Verify that the admin running ADMU has permission to the user's NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors" -Level Error + Write-ToLog -Message "Load/Unload Error: The user registry cannot be loaded or unloaded. Verify that the admin running ADMU has permission to the user's NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors" -Level Warn $Script:ErrorMessage = "Load/Unload Error: User registry cannot be loaded or unloaded. Click the link below for troubleshooting information." } "copy_error" { - Write-ToLog -Message:("Registry Copy Error: The user registry files can not be coppied. Verify that the admin running ADMU has permission to the user's NTUser.dat/ UsrClass.dat files, no user processes/ services are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -Message:("Registry Copy Error: The user registry files can not be coppied. Verify that the admin running ADMU has permission to the user's NTUser.dat/ UsrClass.dat files, no user processes/ services are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "Registry Copy Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." } "rename_registry_file_error" { - Write-ToLog -message:("Registry Rename Error: Could not rename user registry file. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -message:("Registry Rename Error: Could not rename user registry file. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "Registry Rename Error: Registry files cannot be renamed. Click the link below for troubleshooting information." } "backup_error" { - Write-ToLog -Message:("Registry Backup Error: Could not take a backup of the user registry files. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -Message:("Registry Backup Error: Could not take a backup of the user registry files. Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Verify that no user processes/ services for the migration user are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "Registry Backup Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." } "user_init_error" { - Write-ToLog -Message:("User Initialization Error: The new local user was created but could not be initialized. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -Message:("User Initialization Error: The new local user was created but could not be initialized. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "User Initialization Error. Click the link below for troubleshooting information." } "user_create_error" { - Write-ToLog -Message:("User Creation Error: The new local user could not be created. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -Message:("User Creation Error: The new local user could not be created. Verify that the user was not already created before running ADMU. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "User Creation Error. Click the link below for troubleshooting information." } "user_folder_redirection_error" { - Write-ToLog -Message:("User Folder Redirection Error: One of the user's main folder (Desktop, Downloads, Documents, Favorites, Pictures, Videos, Music) path is redirected. Verify that the user's main folders path are set to default and not redirected to another path (ie. Network Drive). Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -Message:("User Folder Redirection Error: One of the user's main folder (Desktop, Downloads, Documents, Favorites, Pictures, Videos, Music) path is redirected. Verify that the user's main folders path are set to default and not redirected to another path (ie. Network Drive). Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "User Folder Redirection Error. Click the link below for troubleshooting information." } Default { - Write-ToLog -Message:("Error occured, please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Error + Write-ToLog -Message:("Error occurred, please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn - $Script:ErrorMessage = "Error occured. Click the link below for troubleshooting information." + $Script:ErrorMessage = "Error occurred. Click the link below for troubleshooting information." } } } \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 index bfc0aa58..e3877dc1 100644 --- a/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Write-ToLog.ps1 @@ -98,7 +98,8 @@ Function Write-ToLog { Update-LogTextBlock -LogText $logMessage -ProgressBar $Script:ProgressBar } # Write log entry to $Path - "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append + Add-Content -Value "$FormattedDate $LevelText $Message" -Path $Path -Encoding utf8 + # "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append -Encoding utf8 } End { diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index ad260f56..c3666649 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -223,7 +223,7 @@ Function Start-Migration { if ($agentInstallStatus) { Write-ToLog -Message:("JumpCloud Agent Install Done") -Level Verbose } else { - Write-ToLog -Message:("JumpCloud Agent Install Failed") -Level Error + Write-ToLog -Message:("JumpCloud Agent Install Failed") -Level Warn Write-ToProgress -ProgressBar $Progressbar -Status "JC Agent Install failed " -form $isForm -logLevel Error exit } @@ -256,7 +256,7 @@ Function Start-Migration { try { Backup-RegistryHive -profileImagePath $oldUserProfileImagePath -SID $SelectedUserSID } catch { - Write-ToLog -Message("Could Not Backup Registry Hives: Exiting...") -Level Error + Write-ToLog -Message("Could Not Backup Registry Hives: Exiting...") -Level Warn Write-ToLog -Message($_.Exception.Message) $admuTracker.backupOldUserReg.fail = $true break @@ -272,8 +272,8 @@ Function Start-Migration { New-localUser -Name $JumpCloudUsername -password $newUserPassword -Description "Created By JumpCloud ADMU" -ErrorVariable userExitCode | Out-Null if ($userExitCode) { - Write-ToLog -Message:("$userExitCode") -Level Error - Write-ToLog -Message:("The user: $JumpCloudUsername could not be created, exiting") -Level Error + Write-ToLog -Message:("$userExitCode") -Level Warn + Write-ToLog -Message:("The user: $JumpCloudUsername could not be created, exiting") -Level Warn Write-AdmuErrorMessage -ErrorName "user_create_error" $admuTracker.newUserCreate.fail = $true break @@ -488,12 +488,12 @@ Function Start-Migration { if ($validateRegistryPermission) { Write-ToLog -Message:("The registry permissions for $($NewUserSID)_admu are correct `n$($validateRegistryPermissionResult | Out-String)") } else { - Write-ToLog -Message:("The registry permissions for $($NewUserSID)_admu are incorrect. Please check permissions SID: $($NewUserSID) ensure Administrators, System, and selected user have have Full Control `n$($validateRegistryPermissionResult | Out-String)") -Level Error + Write-ToLog -Message:("The registry permissions for $($NewUserSID)_admu are incorrect. Please check permissions SID: $($NewUserSID) ensure Administrators, System, and selected user have have Full Control `n$($validateRegistryPermissionResult | Out-String)") -Level Warn } if ($validateRegistryPermissionClasses) { Write-ToLog -Message:("The registry permissions for $($NewUserSID)_Classes_admu are correct `n$($validateRegistryPermissionClassesResult | out-string)") } else { - Write-ToLog -Message:("The registry permissions for $($NewUserSID)_Classes_admu are incorrect. Please check permissions SID: $($NewUserSID) ensure Administrators, System, and selected user have have Full Control `n$($validateRegistryPermissionClassesResult | Out-String)") -Level Error + Write-ToLog -Message:("The registry permissions for $($NewUserSID)_Classes_admu are incorrect. Please check permissions SID: $($NewUserSID) ensure Administrators, System, and selected user have have Full Control `n$($validateRegistryPermissionClassesResult | Out-String)") -Level Warn } $admuTracker.copyRegistry.pass = $true @@ -861,12 +861,12 @@ Function Start-Migration { if ($validateNTUserDatPermissions ) { Write-ToLog -Message:("NTUSER.DAT Permissions are correct $($datPath) `n$($validateNTUserDatPermissionsResults | Out-String)") } else { - Write-ToLog -Message:("NTUSER.DAT Permissions are incorrect. Please check permissions on $($datPath)\NTUSER.DAT to ensure Administrators, System, and selected user have have Full Control `n$($validateNTUserDatPermissionsResults | Out-String)") -level Error + Write-ToLog -Message:("NTUSER.DAT Permissions are incorrect. Please check permissions on $($datPath)\NTUSER.DAT to ensure Administrators, System, and selected user have have Full Control `n$($validateNTUserDatPermissionsResults | Out-String)") -Level Warn } if ($validateUsrClassDatPermissions) { Write-ToLog -Message:("UsrClass.dat Permissions are correct $($datPath)`n$($validateUsrClassDatPermissionsResults | out-string)") } else { - Write-ToLog -Message:("UsrClass.dat Permissions are incorrect. Please check permissions on $($datPath)\AppData\Local\Microsoft\Windows\UsrClass.dat to ensure Administrators, System, and selected user have have Full Control `n$($validateUsrClassDatPermissionsResults | Out-String)") -level Error + Write-ToLog -Message:("UsrClass.dat Permissions are incorrect. Please check permissions on $($datPath)\AppData\Local\Microsoft\Windows\UsrClass.dat to ensure Administrators, System, and selected user have have Full Control `n$($validateUsrClassDatPermissionsResults | Out-String)") -Level Warn } ## End Regedit Block ## @@ -964,8 +964,8 @@ Function Start-Migration { try { Get-Item -Path "$windowsDrive\Windows\uwp_jcadmu.exe" -ErrorAction Stop | Out-Null } catch { - Write-ToLog -Message("Could not find uwp_jcadmu.exe in $windowsDrive\Windows\ UWP Apps will not migrate") -Level Error - Write-ToLog -Message($_.Exception.Message) -Level Error + Write-ToLog -Message("Could not find uwp_jcadmu.exe in $windowsDrive\Windows\ UWP Apps will not migrate") -Level Warn + Write-ToLog -Message($_.Exception.Message) -Level Warn # TODO: Test and return non terminating error here if failure # TODO: Get the checksum # $admuTracker.uwpDownloadExe = $true @@ -1112,7 +1112,7 @@ Function Start-Migration { Write-ToLog -Message:("User: $JumpCloudUserName was not removed from the local system") -Level Verbose } } catch { - Write-ToLog -Message:("Could not remove the $JumpCloudUserName profile and user account") -Level Error + Write-ToLog -Message:("Could not remove the $JumpCloudUserName profile and user account") -Level Warn } $FixedErrors += "$trackedStep" # Create a list of scheduled tasks that are disabled @@ -1124,7 +1124,7 @@ Function Start-Migration { } Default { - # Write-ToLog -Message:("default error") -Level Error + # Write-ToLog -Message:("default error") -Level Warn } } } @@ -1138,9 +1138,9 @@ Function Start-Migration { } else { Write-ToLog -Message:("ADMU encoutered the following errors: $($admuTracker.Keys | Where-Object { $admuTracker[$_].fail -eq $true })") -Level Warn Write-ToLog -Message:("The following migration steps were reverted to their original state: $FixedErrors") -Level Warn - Write-ToLog -Message:('Script finished with errors; Log file location: ' + $jcAdmuLogFile) -Level Error + Write-ToLog -Message:('Script finished with errors; Log file location: ' + $jcAdmuLogFile) -Level Warn Write-ToProgress -ProgressBar $Progressbar -Status $Script:ErrorMessage -form $isForm -logLevel "Error" - throw "JumpCloud ADMU was unable to migrate $selectedUserName" + Throw "JumpCloud ADMU was unable to migrate $selectedUserName" } } } \ No newline at end of file diff --git a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 index a5570466..a5be2dad 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Functions.Tests.ps1 @@ -465,13 +465,6 @@ Describe 'Functions' { } } - Context 'Get-NetBiosName Function' { - # Requires domainjoined system - It 'Get-NetBiosName - JCADB2' -Skip { - Get-NetBiosName | Should -Be 'JCADB2' - } - } - Context 'Convert-SecurityIdentifier Function' { BeforeAll { $newUserPassword = ConvertTo-SecureString -String 'Temp123!' -AsPlainText -Force diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 0e43b679..5b521153 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -126,7 +126,7 @@ Describe 'Migration Test Scenarios' { # check that the FTA/PTA lists contain the $fileType and $protocol variable from the job - Context 'Start-Migration on local accounts (Test Functionallity)' -skip { + Context 'Start-Migration on local accounts (Test Functionality)' -skip { It "username exists for testing" { foreach ($user in $userTestingHash.Values) { $user.username | Should -Not -BeNullOrEmpty From 55eeade7090aa13a365b1f0662d42a837534726d Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 09:28:16 -0700 Subject: [PATCH 36/73] tail and validate local users --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 5b521153..42a864c6 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -496,9 +496,12 @@ Describe 'Migration Test Scenarios' { } Catch { write-host "Migration failed as expected" } + # validate users was removed: + $localUsers = Get-LocalUser + $localUsers.Name | Should -Not -Contain "$JCUSERNAME" if (Test-Path "C:\Windows\Temp\Jcadmu.log") { Write-Host "log located in c drive" - $logContent = Get-Content C:\Windows\Temp\Jcadmu.log + $logContent = Get-Content C:\Windows\Temp\Jcadmu.log -Tail 10 } Write-Host "last lines of log:" Write-Host $logContent From 4bc5b906883194a4bfe740c50968602c53d136f9 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 09:35:48 -0700 Subject: [PATCH 37/73] version of artifact action --- .github/workflows/admu-ci.yml | 4 ++-- .github/workflows/admu-release.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/admu-ci.yml b/.github/workflows/admu-ci.yml index 8e2aa7b4..6f90718a 100644 --- a/.github/workflows/admu-ci.yml +++ b/.github/workflows/admu-ci.yml @@ -122,7 +122,7 @@ jobs: run: | . "${{ github.workspace }}/Deploy/build.ps1" -ModuleVersionType $env:RELEASE_TYPE -ModuleName "JumpCloud.ADMU" - name: Upload Nuspec - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jumpcloud-admu-build path: | @@ -145,7 +145,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jumpcloud-admu-build - uses: actions/cache@v4 diff --git a/.github/workflows/admu-release.yml b/.github/workflows/admu-release.yml index 0658ac0a..b069f255 100644 --- a/.github/workflows/admu-release.yml +++ b/.github/workflows/admu-release.yml @@ -166,7 +166,7 @@ jobs: region: ${{ secrets.AWS_REGION }} version: ${{ env.RELEASE_VERSION }} - name: Upload Release Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jumpcloud-admu path: | @@ -179,7 +179,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download ADMU artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jumpcloud-admu - name: Build Draft Release @@ -208,7 +208,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download ADMU artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jumpcloud-admu - name: Publish From 3b96f202aaba44b0317b2f9449d5fd7c99029b6a Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 10:16:39 -0700 Subject: [PATCH 38/73] add param to build [skip ci] --- Deploy/Build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Deploy/Build.ps1 b/Deploy/Build.ps1 index 336a2f8a..e6ce65eb 100644 --- a/Deploy/Build.ps1 +++ b/Deploy/Build.ps1 @@ -25,7 +25,7 @@ if ($ModuleVersionType -eq 'manual') { . $PSScriptRoot\Build-Module.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) } # Create a new ADMU Template file in this directory for testing/ Building EXE -New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" +New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" -hidePowerShellWindow $true # Run Build-Exe On Windows Systems if ($IsWindows) { . $PSScriptRoot\New-ADMUExe.ps1 From f96388c0b57053eaa94559939544558acc7876f5 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 10:33:03 -0700 Subject: [PATCH 39/73] write out additional debugging --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 42a864c6..1cfb5142 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -499,12 +499,16 @@ Describe 'Migration Test Scenarios' { # validate users was removed: $localUsers = Get-LocalUser $localUsers.Name | Should -Not -Contain "$JCUSERNAME" - if (Test-Path "C:\Windows\Temp\Jcadmu.log") { + if (Test-Path "C:\Windows\Temp\jcAdmu.log") { Write-Host "log located in c drive" - $logContent = Get-Content C:\Windows\Temp\Jcadmu.log -Tail 10 + $logContent = Get-Content C:\Windows\Temp\jcAdmu.log -Tail 10 } Write-Host "last lines of log:" Write-Host $logContent + + # another should statement: + $logContent.Contains("The following migration steps were reverted to their original state: newUserInit") | Should -Be $true + if ($logContent -match "The following migration steps were reverted to their original state: newUserInit") { write-host "Start Migration Task Failed Successfully" return $true @@ -527,8 +531,10 @@ Describe 'Migration Test Scenarios' { Wait-Job -Job $waitStartMigrationJob | Out-Null $SMData = Receive-Job -Job $waitStartMigrationJob -Keep # start migration should return $true if the job completes and fails as expected (should be true) + Get-Content C:\Windows\Temp\jcAdmu.log -Tail 10 $SMData | should -be $true # the task should have been disabled during the start migration script (should be $true) + Get-ScheduledTask -TaskName "TestTaskFail" $disabledTaskData | should -be $true # the migration user should exist "C:\Users\$($user.username)" | Should -Exist From ac8070476106d124af0513fc0e9ffa1e010a8c58 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 10:44:39 -0700 Subject: [PATCH 40/73] CUT-4541 - reinstall search/ start appx for all devices (not just windows 10) --- .../Powershell/Public/Start-Migration.ps1 | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index c3666649..4069b533 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -424,23 +424,20 @@ Function Start-Migration { } } - # for Windows 10 devices, force refresh of start/ search app: - If ($systemVersion.OSName -Match "Windows 10") { - Write-ToLog -Message:('Windows 10 System, removing start and search reg keys to force refresh of those apps') - $regKeyClear = @( - "SOFTWARE\Microsoft\Windows\CurrentVersion\StartLayout", - "SOFTWARE\Microsoft\Windows\CurrentVersion\Start", - "SOFTWARE\Microsoft\Windows\CurrentVersion\SearchSettings", - "SOFTWARE\Microsoft\Windows\CurrentVersion\Search" - ) - - foreach ($key in $regKeyClear) { - if (reg query "HKU\$($NewUserSID)_admu\$($key)") { - write-ToLog -Message:("removing key: $key") - reg delete "HKU\$($NewUserSID)_admu\$($key)" /f - } else { - write-ToLog -Message:("key not found $key") - } + # Force refresh of start/ search apps: + Write-ToLog -Message:('Removing start and search reg keys to force reinstall of those apps on first login') + $regKeyClear = @( + "SOFTWARE\Microsoft\Windows\CurrentVersion\StartLayout", + "SOFTWARE\Microsoft\Windows\CurrentVersion\Start", + "SOFTWARE\Microsoft\Windows\CurrentVersion\SearchSettings", + "SOFTWARE\Microsoft\Windows\CurrentVersion\Search" + ) + foreach ($key in $regKeyClear) { + if (reg query "HKU\$($NewUserSID)_admu\$($key)") { + write-ToLog -Message:("removing key: $key") + reg delete "HKU\$($NewUserSID)_admu\$($key)" /f + } else { + write-ToLog -Message:("key not found $key") } } From cb7d1a92869690299702764ea51eaa6c59dc45c9 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 11:47:10 -0700 Subject: [PATCH 41/73] fixed migration tests by importing the module instead of calling start migration (which no longer contains any private functions) --- .../Powershell/Tests/Migration.Tests.ps1 | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 1cfb5142..a373b492 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -49,13 +49,13 @@ BeforeAll { InitUser -UserName $($User.Username) -Password $($User.Password) } - $config = get-content 'C:\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf' - $regex = 'systemKey\":\"(\w+)\"' - $systemKey = [regex]::Match($config, $regex).Groups[1].Value + # $config = get-content 'C:\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf' + # $regex = 'systemKey\":\"(\w+)\"' + # $systemKey = [regex]::Match($config, $regex).Groups[1].Value # Remove users with ADMU_ prefix # Remove Created Users - Get-JCuser -username "ADMU_*" | Remove-JCuser -Force + Get-JCUser -username "ADMU_*" | Remove-JCUser -Force } Describe 'Migration Test Scenarios' { Enable-TestNameAsVariablePlugin @@ -63,7 +63,7 @@ Describe 'Migration Test Scenarios' { Write-Host "---------------------------" Write-Host "Begin Test: $testName`n" } - Context 'Test FTA/PTA CSV Creation' -skip { + Context 'Test FTA/PTA CSV Creation' { It 'Creates FTA/PTA CSV files and changes file/protocol associations' { $Password = "Temp123!" $localUser = "ADMU_" + -join ((65..90) + (97..122) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) @@ -126,7 +126,7 @@ Describe 'Migration Test Scenarios' { # check that the FTA/PTA lists contain the $fileType and $protocol variable from the job - Context 'Start-Migration on local accounts (Test Functionality)' -skip { + Context 'Start-Migration on local accounts (Test Functionality)' { It "username exists for testing" { foreach ($user in $userTestingHash.Values) { $user.username | Should -Not -BeNullOrEmpty @@ -225,7 +225,7 @@ Describe 'Migration Test Scenarios' { } } - Context 'Start-Migration kicked off through JumpCloud agent' -skip { + Context 'Start-Migration kicked off through JumpCloud agent' { BeforeAll { # test connection to Org $Org = Get-JcSdkOrganization @@ -322,7 +322,7 @@ Describe 'Migration Test Scenarios' { } } } - Context 'Start-Migration Successfully Binds JumpCloud User to System' -skip { + Context 'Start-Migration Successfully Binds JumpCloud User to System' { It 'user bound to system after migration' { $headers = @{} $headers.Add("x-org-id", $env:PESTER_ORGID) @@ -369,7 +369,7 @@ Describe 'Migration Test Scenarios' { } } } - Context 'Start-Migration Fails to Bind JumpCloud User to System and throws error' -skip { + Context 'Start-Migration Fails to Bind JumpCloud User to System and throws error' { It 'user bound to system after migration' { Write-Host "`nBegin Test: Start-Migration Fails to Bind JumpCloud User to System and throws error" $Password = "Temp123!" @@ -380,7 +380,7 @@ Describe 'Migration Test Scenarios' { { Start-Migration -JumpCloudAPIKey $env:PESTER_APIKEY -AutobindJCUser $true -JumpCloudUserName "$($user2)" -SelectedUserName "$ENV:COMPUTERNAME\$($user1)" -TempPassword "$($Password)" } | Should -Throw } } - Context 'Start-Migration Fails when LocalUsername and JumpCloudUsername are the same' -skip { + Context 'Start-Migration Fails when LocalUsername and JumpCloudUsername are the same' { It 'local and JumpCloud usernames are the same' { Write-Host "`nStart-Migration Fails when LocalUsername and JumpCloudUsername are the same" $Password = "Temp123!" @@ -407,7 +407,7 @@ Describe 'Migration Test Scenarios' { foreach ($user in $JCReversionHash.Values) { # Begin background job before Start-Migration # define path to start migration for parallel job: - $pathToSM = "$PSScriptRoot\..\Public\Start-Migration.ps1" + $pathToSM = "$PSScriptRoot\..\..\JumpCloud.ADMU.psd1" Write-Host "$(Get-Date -UFormat "%D %r") - Start parallel job to wait for new user directory" $waitJob = Start-Job -ScriptBlock:( { @@ -451,8 +451,6 @@ Describe 'Migration Test Scenarios' { $task = Get-ScheduledTask -TaskName "TestTaskFail" do { $task = Get-ScheduledTask -TaskName "TestTaskFail" - Start-Sleep -Seconds:(1) - } Until ($task.state -eq "Disabled") if ($task.state -eq "Disabled") { @@ -484,7 +482,7 @@ Describe 'Migration Test Scenarios' { $date = Get-Date -UFormat "%D %r" Write-Host "$date - Starting Start migration:" Write-Host "$date - path: $SMPath" - . $SMPath + Import-Module $SMPath -Force if ($?) { Write-Host "imported start migration" } else { @@ -503,12 +501,6 @@ Describe 'Migration Test Scenarios' { Write-Host "log located in c drive" $logContent = Get-Content C:\Windows\Temp\jcAdmu.log -Tail 10 } - Write-Host "last lines of log:" - Write-Host $logContent - - # another should statement: - $logContent.Contains("The following migration steps were reverted to their original state: newUserInit") | Should -Be $true - if ($logContent -match "The following migration steps were reverted to their original state: newUserInit") { write-host "Start Migration Task Failed Successfully" return $true From bc6033f89b146db9a14ded8adb0707656f7859aa Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 11:47:46 -0700 Subject: [PATCH 42/73] variable names and logging changes --- Deploy/Build-Module.ps1 | 6 +++--- .../Private/Backup-RegistryHive.ps1 | 2 +- .../Powershell/Private/Get-DomainStatus.ps1 | 4 ++-- .../Private/Get-MtpOrganization.ps1 | 4 ++-- .../Private/Set-JCUserToSystemAssociation.ps1 | 4 ++-- .../Private/Set-UserRegistryLoadState.ps1 | 8 ++++---- .../Powershell/Private/Test-LocalUserName.ps1 | 10 +++++----- .../Private/Test-RegistryValueMatch.ps1 | 10 +++++----- .../Private/Test-UserFolderRedirect.ps1 | 2 +- .../Powershell/Private/Test-UsernameOrSid.ps1 | 20 +++++++++---------- .../Private/Write-AdmuErrorMessage.ps1 | 2 +- .../Powershell/Public/Start-Migration.ps1 | 2 +- 12 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Deploy/Build-Module.ps1 b/Deploy/Build-Module.ps1 index b4b0b4c2..dbbdd75b 100644 --- a/Deploy/Build-Module.ps1 +++ b/Deploy/Build-Module.ps1 @@ -22,8 +22,8 @@ Write-Host ('[status]Check PowerShell Gallery for module version info') if ($ManualModuleVersion) { $ManualModuleVersionRetrieval = Get-Content -Path:($FilePath_psd1) | Where-Object { $_ -like '*ModuleVersion*' } $SemanticRegex = [Regex]"[0-9]+.[0-9]+.[0-9]+" - $SemeanticVersion = Select-String -InputObject $ManualModuleVersionRetrieval -pattern ($SemanticRegex) - $ModuleVersion = $SemeanticVersion[0].Matches.Value + $semanticVersion = Select-String -InputObject $ManualModuleVersionRetrieval -pattern ($SemanticRegex) + $ModuleVersion = $semanticVersion[0].Matches.Value } else { $PSGalleryInfo = Get-PSGalleryModuleVersion -Name:($ModuleName) -ReleaseType:($ModuleVersionType) #('Major', 'Minor', 'Patch') $ModuleVersion = $PSGalleryInfo.NextVersion @@ -62,7 +62,7 @@ If ($ModuleChangelogVersion -ne $PSD1Version) { ($NewModuleChangelogRecord + ($ModuleChangelog | Out-String)).Trim() | Set-Content -Path:($FilePath_ModuleChangelog) -Force } else { # Get content between latest version and last - $ModuleChangelogContent = Get-Content -Path:($FilePath_ModuleChangelog) | Select -First 3 + $ModuleChangelogContent = Get-Content -Path:($FilePath_ModuleChangelog) | Select-Object -First 3 $ReleaseDateRegex = [regex]'(?<=Release Date:\s)(.*)' $ReleaseDateRegexMatch = $ModuleChangelogContent | Select-String -Pattern $ReleaseDateRegex $ReleaseDate = $ReleaseDateRegexMatch.Matches.Value diff --git a/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 b/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 index e6f05a08..bb123c14 100644 --- a/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Backup-RegistryHive.ps1 @@ -21,7 +21,7 @@ Function Backup-RegistryHive { $processList = Get-ProcessByOwner -username $domainUsername if ($processList) { Show-ProcessListResult -ProcessList $processList -domainUsername $domainUsername - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + # $CloseResults = Close-ProcessByOwner -ProcessList $processList -force $ADMU_closeProcess } try { Write-ToLog -Message("Initial backup was not successful, trying again...") diff --git a/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 index 4847e9da..bc88bf3a 100644 --- a/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Get-DomainStatus.ps1 @@ -2,10 +2,10 @@ function Get-DomainStatus { $ADStatus = dsregcmd.exe /status foreach ($line in $ADStatus) { if ($line -match "AzureADJoined : ") { - $AzureADStatus = ($line.trimstart('AzureADJoined : ')) + $AzureADStatus = ($line.TrimStart('AzureADJoined : ')) } if ($line -match "DomainJoined : ") { - $LocalDomainStatus = ($line.trimstart('DomainJoined : ')) + $LocalDomainStatus = ($line.TrimStart('DomainJoined : ')) } } # Return both statuses diff --git a/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 b/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 index 67853792..e4b937b3 100644 --- a/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Get-MtpOrganization.ps1 @@ -48,7 +48,7 @@ function Get-mtpOrganization { Write-ToLog -Message "API Key Validated`nOrgName: $($results.DisplayName)" $orgs = $results._id, $results.DisplayName } elseif (($results.count -gt 1)) { - Write-ToLog -Message "Found $($results.count) orgs with the specifed API Key" + Write-ToLog -Message "Found $($results.count) orgs with the specified API Key" # initial prompt for MTP selection switch ($inputType) { $true { @@ -68,7 +68,7 @@ function Get-mtpOrganization { } end { - #returned org as an object [0]=id [1]=dispalyName + #returned org as an object [0]=id [1]=displayName return $orgs } } diff --git a/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 index b79615ba..eb4ce070 100644 --- a/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Set-JCUserToSystemAssociation.ps1 @@ -56,10 +56,10 @@ function Set-JCUserToSystemAssociation { End { # Associations post should return 204 success no content if ($StatusCode -eq 204) { - Write-ToLog -Message:("Associations Endpoint returened statusCode $statusCode [success]") -Level:('Warn') + Write-ToLog -Message:("Associations Endpoint returned statusCode $statusCode [success]") -Level:('Warn') return $true } else { - Write-ToLog -Message:("Associations Endpoint returened statusCode $statusCode | $errorMsg") -Level:('Warn') + Write-ToLog -Message:("Associations Endpoint returned statusCode $statusCode | $errorMsg") -Level:('Warn') return $false } } diff --git a/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 b/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 index d763171e..3a5fffad 100644 --- a/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Set-UserRegistryLoadState.ps1 @@ -51,7 +51,7 @@ function Set-UserRegistryLoadState { $processList = Get-ProcessByOwner -username $username if ($processList) { Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + # $CloseResults = Close-ProcessByOwner -ProcessList $processList -force $ADMU_closeProcess } Set-UserRegistryLoadstate -op Load -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive root } @@ -65,7 +65,7 @@ function Set-UserRegistryLoadState { $processList = Get-ProcessByOwner -username $username if ($processList) { Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + # $CloseResults = Close-ProcessByOwner -ProcessList $processList -force $ADMU_closeProcess } Set-UserRegistryLoadstate -op Load -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive classes } @@ -87,7 +87,7 @@ function Set-UserRegistryLoadState { $processList = Get-ProcessByOwner -username $username if ($processList) { Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + # $CloseResults = Close-ProcessByOwner -ProcessList $processList -force $ADMU_closeProcess } Set-UserRegistryLoadstate -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive root } @@ -103,7 +103,7 @@ function Set-UserRegistryLoadState { $processList = Get-ProcessByOwner -username $username if ($processList) { Show-ProcessListResult -ProcessList $processList -domainUsername $username - # $CloseResults = Close-ProcessByOwner -ProcesssList $processList -force $ADMU_closeProcess + # $CloseResults = Close-ProcessByOwner -ProcessList $processList -force $ADMU_closeProcess } Set-UserRegistryLoadstate -op "Unload" -ProfilePath $ProfilePath -UserSid $UserSid -counter $counter -hive classes } diff --git a/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 index a0a9195e..6a8c3a65 100644 --- a/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Test-LocalUserName.ps1 @@ -1,4 +1,4 @@ -function Test-Localusername { +function Test-LocalUsername { [CmdletBinding()] param ( [system.array] $field @@ -6,19 +6,19 @@ function Test-Localusername { begin { $win32UserProfiles = Get-WmiObject -Class:('Win32_UserProfile') -Property * | Where-Object { $_.Special -eq $false } $users = $win32UserProfiles | Select-Object -ExpandProperty "SID" | Convert-SecurityIdentifier - $localusers = new-object system.collections.arraylist + $localUsers = new-object System.Collections.ArrayList foreach ($username in $users) { $domain = ($username -split '\\')[0] if ($domain -match $env:computername) { - $localusertrim = $username -creplace '^[^\\]*\\', '' - $localusers.Add($localusertrim) | Out-Null + $localUserTrim = $username -creplace '^[^\\]*\\', '' + $localUsers.Add($localUserTrim) | Out-Null } } } process { - if ($localusers -eq $field) { + if ($localUsers -eq $field) { Return $true } else { Return $false diff --git a/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 index ec6631f2..731d37d1 100644 --- a/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Test-RegistryValueMatch.ps1 @@ -9,21 +9,21 @@ function Test-RegistryValueMatch { [ValidateNotNullOrEmpty()]$Value, [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()]$stringmatch + [ValidateNotNullOrEmpty()]$stringMatch ) $ErrorActionPreference = "SilentlyContinue" - $regvalue = Get-ItemPropertyValue -Path $Path -Name $Value + $regValue = Get-ItemPropertyValue -Path $Path -Name $Value $ErrorActionPreference = "Continue" $out = 'Value For ' + $Value + ' Is ' + $1 + ' On ' + $Path - if ([string]::IsNullOrEmpty($regvalue)) { - write-host 'KEY DOESNT EXIST OR IS EMPTY' + if ([string]::IsNullOrEmpty($regValue)) { + write-host 'KEY DOES NOT EXIST OR IS EMPTY' return $false } else { - if ($regvalue -match ($stringmatch)) { + if ($regValue -match ($stringMatch)) { Write-Host $out return $true } else { diff --git a/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 index 27596bc5..1af548da 100644 --- a/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Test-UserFolderRedirect.ps1 @@ -6,7 +6,7 @@ function Test-UserFolderRedirect { $UserSid ) begin { - if ("HKEY_USERS" -notin (Get-psdrive | select-object name).Name) { + if ("HKEY_USERS" -notin (Get-PSDrive | select-object name).Name) { Write-ToLog "Mounting HKEY_USERS" New-PSDrive -Name:("HKEY_USERS") -PSProvider:("Registry") -Root:("HKEY_USERS") | Out-Null } diff --git a/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 b/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 index a26ca620..de051c1a 100644 --- a/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Test-UsernameOrSid.ps1 @@ -1,17 +1,17 @@ -function Test-UsernameOrSID { +function Test-usernameOrSID { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - $usernameorsid + $usernameOrSID ) Begin { $sidPattern = "^S-\d-\d+-(\d+-){1,14}\d+$" - $localcomputersidprefix = ((Get-LocalUser | Select-Object -First 1).SID).AccountDomainSID.ToString() - $convertedUser = Convert-UserName $usernameorsid - $registyProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + $localComputerIDPrefix = ((Get-LocalUser | Select-Object -First 1).SID).AccountDomainSID.ToString() + $convertedUser = Convert-UserName $usernameOrSID + $registryProfiles = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $list = @() - foreach ($profile in $registyProfiles) { + foreach ($profile in $registryProfiles) { $list += Get-ItemProperty -Path $profile.PSPath | Select-Object PSChildName, ProfileImagePath } $users = @() @@ -28,14 +28,14 @@ function Test-UsernameOrSID { } process { #check if sid, if valid sid and return sid - if ([regex]::IsMatch($usernameorsid, $sidPattern)) { - if (($usernameorsid -in $users.SID) -And !($users.SID.Contains($localcomputersidprefix))) { + if ([regex]::IsMatch($usernameOrSID, $sidPattern)) { + if (($usernameOrSID -in $users.SID) -And !($users.SID.Contains($localComputerIDPrefix))) { # return, it's a valid SID Write-ToLog "valid sid returning sid" - return $usernameorsid + return $usernameOrSID } } elseif ([regex]::IsMatch($convertedUser, $sidPattern)) { - if (($convertedUser -in $users.SID) -And !($users.SID.Contains($localcomputersidprefix))) { + if (($convertedUser -in $users.SID) -And !($users.SID.Contains($localComputerIDPrefix))) { # return, it's a valid SID Write-ToLog "valid user returning sid" return $convertedUser diff --git a/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 b/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 index 481e780f..15d559a0 100644 --- a/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/Write-AdmuErrorMessage.ps1 @@ -9,7 +9,7 @@ function Write-AdmuErrorMessage { $Script:ErrorMessage = "Load/Unload Error: User registry cannot be loaded or unloaded. Click the link below for troubleshooting information." } "copy_error" { - Write-ToLog -Message:("Registry Copy Error: The user registry files can not be coppied. Verify that the admin running ADMU has permission to the user's NTUser.dat/ UsrClass.dat files, no user processes/ services are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn + Write-ToLog -Message:("Registry Copy Error: The user registry files can not be copied. Verify that the admin running ADMU has permission to the user's NTUser.dat/ UsrClass.dat files, no user processes/ services are running. Please refer to this link for more information: https://github.com/TheJumpCloud/jumpcloud-ADMU/wiki/troubleshooting-errors") -Level Warn $Script:ErrorMessage = "Registry Copy Error: Verify that the admin running ADMU has permission to NTUser.dat/UsrClass.dat. Click the link below for troubleshooting information." } diff --git a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 index 4069b533..1fef5771 100644 --- a/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 +++ b/jumpcloud-ADMU/Powershell/Public/Start-Migration.ps1 @@ -285,7 +285,7 @@ Function Start-Migration { $NewUserSID = New-LocalUserProfile -username:($JumpCloudUsername) -ErrorVariable profileInit if ($profileInit) { Write-ToLog -Message:("$profileInit") - Write-ToLog -Message:("The user: $JumpCloudUsername could not be initalized, exiting") + Write-ToLog -Message:("The user: $JumpCloudUsername could not be initialized, exiting") Write-AdmuErrorMessage -ErrorName "user_init_error" $admuTracker.newUserInit.fail = $true break From c4c5db1a481c8bf2f4200fa74f4311a4c3e9f704 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Thu, 16 Jan 2025 12:26:52 -0700 Subject: [PATCH 43/73] set systemKey in migration tests --- jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index a373b492..07c7c9d5 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -49,9 +49,9 @@ BeforeAll { InitUser -UserName $($User.Username) -Password $($User.Password) } - # $config = get-content 'C:\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf' - # $regex = 'systemKey\":\"(\w+)\"' - # $systemKey = [regex]::Match($config, $regex).Groups[1].Value + $config = get-content 'C:\Program Files\JumpCloud\Plugins\Contrib\jcagent.conf' + $regex = 'systemKey\":\"(\w+)\"' + $systemKey = [regex]::Match($config, $regex).Groups[1].Value # Remove users with ADMU_ prefix # Remove Created Users @@ -350,7 +350,7 @@ Describe 'Migration Test Scenarios' { Write-Host "## GeneratedUser Username: $($generatedUser.Username)`n" write-host "`nRunning: Start-Migration -JumpCloudUserName $($user.JCUsername) -SelectedUserName $($user.username) -TempPassword $($user.password)`n" { Start-Migration -JumpCloudAPIKey $env:PESTER_APIKEY -AutobindJCUser $true -JumpCloudUserName "$($user.JCUsername)" -SelectedUserName "$ENV:COMPUTERNAME\$($user.username)" -TempPassword "$($user.password)" -UpdateHomePath $user.UpdateHomePath -BindAsAdmin $user.BindAsAdmin } | Should -Not -Throw - $association = Get-JcSdkSystemAssociation -systemid $systemKey -Targets user | Where-Object { $_.ToId -eq $($GeneratedUser.Id) } + $association = Get-JcSdkSystemAssociation -SystemId $systemKey -Targets user | Where-Object { $_.ToId -eq $($GeneratedUser.Id) } Write-Host "`n## Validating sudo status on $($GeneratedUser.Id) | Should be ($($user.BindAsAdmin)) on $systemKey" $association | Should -not -BeNullOrEmpty From ed36b4b83b90caf5b3b09c4ac00b453fbde5474b Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Fri, 17 Jan 2025 13:39:24 -0700 Subject: [PATCH 44/73] Variable names --- .gitignore | 3 ++- .vscode/settings.json | 14 ++++++++++ Deploy/Build.ps1 | 2 +- Deploy/New-AdmuExe.ps1 | 26 ++++++++++++------- ModuleChangelog.md | 2 +- jumpcloud-ADMU/JumpCloud.ADMU.psd1 | 4 +-- .../Powershell/Tests/Migration.Tests.ps1 | 10 +++---- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index b770aa51..bac0f055 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ test_results JumpCloud.ADMU.nuspec **/.nupkg -Deploy/ADMU.ps1 \ No newline at end of file +Deploy/ADMU.ps1 +Deploy/admuTemplate.ps1 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b345666..1b2a43a5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "spellright.language": ["en"], "spellright.documentTypes": [ "markdown", @@ -7,12 +8,25 @@ "yaml", "powershell" ], + // powershell settings "powershell.scriptAnalysis.settingsPath": "jumpcloud-ADMU/Powershell/Tests/PSScriptAnalyzerSettings.psd1", "powershell.codeFormatting.preset": "OTBS", "powershell.scriptAnalysis.enable": true, + // file formatting & saving: + "prettier.enable": true, + "editor.formatOnSave": true, "files.trimTrailingWhitespace": true, "search.useIgnoreFiles": false, + // cSpell: "cSpell.words": [ "ADMU", + ], + "cSpell.language": "en", + "cSpell.enabled": true, + "cSpell.enabledFiletypes": [ + "latex", + "markdown", + "plaintext", + "powershell" ] } diff --git a/Deploy/Build.ps1 b/Deploy/Build.ps1 index e6ce65eb..6ca26d9e 100644 --- a/Deploy/Build.ps1 +++ b/Deploy/Build.ps1 @@ -24,7 +24,7 @@ if ($ModuleVersionType -eq 'manual') { } else { . $PSScriptRoot\Build-Module.ps1 -ModuleVersionType:($ModuleVersionType) -ModuleName:($ModuleName) } -# Create a new ADMU Template file in this directory for testing/ Building EXE +# Create a new ADMU Template file in this directory for testing/ Building EXE (default hide debug pwsh window) New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" -hidePowerShellWindow $true # Run Build-Exe On Windows Systems if ($IsWindows) { diff --git a/Deploy/New-AdmuExe.ps1 b/Deploy/New-AdmuExe.ps1 index 98b8f294..425ff07f 100644 --- a/Deploy/New-AdmuExe.ps1 +++ b/Deploy/New-AdmuExe.ps1 @@ -21,6 +21,11 @@ $FormPath = $FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Private\Dis $VersionFormRegex = [regex]'(?<=Title="JumpCloud ADMU )([0-9]+)\.([0-9]+)\.([0-9]+)' $VersionMatchForm = Select-String -Path:($FormPath) -Pattern:($VersionFormRegex) $FormVersion = $VersionMatchForm.Matches.Value +# ProgressForm.ps1 Variables +$progressFormPath = $FolderPath_ModuleRootPath + '\jumpcloud-ADMU\Powershell\Private\DisplayForms\ProgressForm.ps1' +$versionProgressFormRegex = [regex]'(?<=Title="JumpCloud ADMU )([0-9]+)\.([0-9]+)\.([0-9]+)' +$versionMatchProgressForm = Select-String -Path:($progressFormPath) -Pattern:($versionProgressFormRegex) +$progressFormVersion = $versionMatchProgressForm.Matches.Value # .psd1 Variables $PSD1Path = $FolderPath_ModuleRootPath + '\jumpcloud-ADMU\JumpCloud.ADMU.psd1' $VersionPsd1Regex = [regex]"(?<=ModuleVersion\s*=\s*')(([0-9]+)\.([0-9]+)\.([0-9]+))" @@ -33,18 +38,21 @@ $year = Get-Date -Format "yyyy" # Write out diagnostic information Write-Host "[JumpCloud ADMU Build Configuration]" Write-Host "Form Version: $FormVersion" +Write-Host "ProgressForm Version: $progressFormVersion" Write-Host "Psd1 Version: $PSD1Version" -# Validate Version +# Validate Versions $FormVersion | Should -Be $PSD1Version +$progressFormVersion | Should -Be $PSD1Version -# Clear existing file -If (Test-Path -Path:("$PSScriptRoot/admuTemplate.ps1")) { - "A Template file exists" -} else { +# Check for a template file: +If (-Not (Test-Path -Path:("$PSScriptRoot/admuTemplate.ps1"))) { + "A Template file does not exist, creating template file" # Build ADMU.PS1 File: . $PSScriptRoot\New-ADMUTemplate.ps1 New-ADMUTemplate -ExportPath "$PSScriptRoot/admuTemplate.ps1" +} +if (-Not (Test-Path -Path:("$PSScriptRoot/admuTemplate.ps1"))) { throw "A template file does not exist, an EXE can not be built." } @@ -60,9 +68,9 @@ If (-Not (Get-InstalledModule -Name ps2exe -ErrorAction Ignore)) { Import-Module -Name ps2exe If (-not [System.String]::IsNullOrEmpty($PSD1Version)) { $guiOutputPath = ($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\exe\gui_jcadmu.exe') - Invoke-ps2exe -inputFile "$PSScriptRoot/admuTemplate.ps1" -outputFile $guiOutputPath -title 'JumpCloud ADMU' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -requireAdmin -iconfile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') + Invoke-ps2exe -inputFile "$PSScriptRoot/admuTemplate.ps1" -outputFile $guiOutputPath -title 'JumpCloud ADMU' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -requireAdmin -iconFile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') $guiExeFile = Get-Item $guiOutputPath - $guiHash = (get-filehash -algorithm SHA256 -path $guiExeFile).Hash + $guiHash = (Get-FileHash -algorithm SHA256 -path $guiExeFile).Hash Write-Host "==== GUI_JCADMU.EXE Build Status ====" Write-Host "Version: $($guiExeFile.VersionInfo.FileVersionRaw)" Write-Host "Build Date: $($guiExeFile.CreationTime)" @@ -77,9 +85,9 @@ $uwpPath = $FolderPath_ModuleRootPath + '\Deploy\uwp_jcadmu.ps1' # Always generate a new UWP EXE try { $uwpOutputPath = ($FolderPath_ModuleRootPath + '\jumpcloud-ADMU\exe\uwp_jcadmu.exe') - Invoke-ps2exe -inputFile ($uwpPath) -outputFile $uwpOutputPath -title 'JumpCloud ADMU UWP Fix' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility UWP Fix Executable' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -iconfile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') + Invoke-ps2exe -inputFile ($uwpPath) -outputFile $uwpOutputPath -title 'JumpCloud ADMU UWP Fix' -product 'JumpCloud ADMU' -description 'JumpCloud AD Migration Utility UWP Fix Executable' -copyright "(c) $year" -version $Psd1Version -company 'JumpCloud' -iconFile ($FolderPath_ModuleRootPath + '\Deploy\admu.ico') $uwpExeFile = Get-Item $uwpOutputPath - $uwpHash = (get-filehash -algorithm SHA256 -path $uwpExeFile).Hash + $uwpHash = (Get-FileHash -algorithm SHA256 -path $uwpExeFile).Hash Write-Host "==== UWP_JCADMU.EXE Build Status ====" Write-Host "Version: $($uwpExeFile.VersionInfo.FileVersionRaw)" Write-Host "Build Date: $($uwpExeFile.CreationTime)" diff --git a/ModuleChangelog.md b/ModuleChangelog.md index 18bf7456..95a18d95 100644 --- a/ModuleChangelog.md +++ b/ModuleChangelog.md @@ -1,6 +1,6 @@ ## 2.7.11 -Release Date: January 13, 2025 +Release Date: January 17, 2025 #### RELEASE NOTES diff --git a/jumpcloud-ADMU/JumpCloud.ADMU.psd1 b/jumpcloud-ADMU/JumpCloud.ADMU.psd1 index 08489855..9567e786 100644 --- a/jumpcloud-ADMU/JumpCloud.ADMU.psd1 +++ b/jumpcloud-ADMU/JumpCloud.ADMU.psd1 @@ -3,7 +3,7 @@ # # Generated by: JumpCloud Customer Tools Team # -# Generated on: 1/13/2025 +# Generated on: 1/16/2025 # @{ @@ -18,7 +18,7 @@ ModuleVersion = '2.7.11' # CompatiblePSEditions = @() # ID used to uniquely identify this module -GUID = '192580de-87dc-4853-ab18-c4e59c5bb36e' +GUID = '8c59eabf-3445-4b50-a0ba-33c01e5569b1' # Author of this module Author = 'JumpCloud Customer Tools Team' diff --git a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 index 07c7c9d5..cfb89683 100644 --- a/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 +++ b/jumpcloud-ADMU/Powershell/Tests/Migration.Tests.ps1 @@ -130,7 +130,7 @@ Describe 'Migration Test Scenarios' { It "username exists for testing" { foreach ($user in $userTestingHash.Values) { $user.username | Should -Not -BeNullOrEmpty - $user.JCusername | Should -Not -BeNullOrEmpty + $user.JCUsername | Should -Not -BeNullOrEmpty Get-LocalUser $user.username | Should -Not -BeNullOrEmpty } } @@ -263,7 +263,7 @@ Describe 'Migration Test Scenarios' { foreach ($result in $results) { # Delete Command Results Write-Host "Found Command Results: $($result.id) removing..." - remove-jcsdkcommandresult -id $result.id + Remove-JcSdkCommandResult -id $result.id } # Clear previous commands matching the name $RemoteADMUCommands = Get-JcSdkCommand | Where-Object { $_.name -eq $CommandName } @@ -287,7 +287,7 @@ Describe 'Migration Test Scenarios' { $results = Get-JcSdkCommandResult foreach ($result in $results) { # Delete Command Results - remove-jcsdkcommandresult -id $result.id + Remove-JcSdkCommandResult -id $result.id } # begin tests foreach ($user in $JCCommandTestingHash.Values) { @@ -332,7 +332,7 @@ Describe 'Migration Test Scenarios' { foreach ($user in $JCFunctionalHash.Values) { Write-Host "`n## Begin Bind User Test ##" Write-Host "## $($user.Username) Bound as Admin: $($user.BindAsAdmin) ##`n" - $users = Get-JCSDKUser + $users = Get-JcSdkUser if ("$($user.JCUsername)" -in $users.Username) { $existing = $users | Where-Object { $_.username -eq "$($user.JCUsername)" } Write-Host "Found JumpCloud User, $($existing.Id) removing..." @@ -547,7 +547,7 @@ Describe 'Migration Test Scenarios' { AfterAll { $systems = Get-JCSdkSystem - $CIsystems = $systems | Where-Object { $_.displayname -match "fv-az*" } + $CIsystems = $systems | Where-Object { $_.DisplayName -match "fv-az*" } foreach ($system in $CIsystems) { Remove-JcSdkSystem -id $system.Id } From a57a0740d2c7b58b8f1947a9817f7847b47d1525 Mon Sep 17 00:00:00 2001 From: Joe Workman Date: Mon, 20 Jan 2025 16:50:20 -0700 Subject: [PATCH 45/73] form elements, update to xaml form for MTP selection --- .../Powershell/Private/DisplayForms/Form.ps1 | 48 +++-- .../Private/DisplayForms/ProgressForm.ps1 | 2 +- .../DisplayForms/Show-MtpSelection.ps1 | 190 ++++++++++-------- .../Private/Get-MtpOrganization.ps1 | 5 +- 4 files changed, 141 insertions(+), 104 deletions(-) diff --git a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 index 8d6097a7..05b68b8f 100644 --- a/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 +++ b/jumpcloud-ADMU/Powershell/Private/DisplayForms/Form.ps1 @@ -5,7 +5,24 @@ Function Show-SelectionForm { #============================================================================================== # XAML Code - Imported from Visual Studio WPF Application #============================================================================================== - [void][System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework') + $types = @( + 'PresentationFramework', + 'PresentationCore', + 'System.Windows.Forms', + 'System.Drawing', + 'WindowsBase' + ) + foreach ($type in $types) { + if (-not ([System.Management.Automation.PSTypeName]$type).Type) { + [void][System.Reflection.Assembly]::LoadWithPartialName($type) + Add-Type -AssemblyName $type + } + } + # [void][System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework') + # [void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") + # [void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") + # # Add-Type -AssemblyName System.Windows.Forms + # Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Windows.Forms, System.Drawing [xml]$XAML = @' + +