From afabea50318fe5a11adcbe7aecfbc0d37faba095 Mon Sep 17 00:00:00 2001 From: Reto Schelbert Date: Tue, 11 May 2021 21:25:38 +0200 Subject: [PATCH 1/3] refactoring json data, performance, simplicity --- clientSide/cyberArkRoyal.ps1 | 74 ++++++++++---------------- serverSide/cyberarkSafeAccountList.ps1 | 24 +++++---- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/clientSide/cyberArkRoyal.ps1 b/clientSide/cyberArkRoyal.ps1 index 84111fc..39b5a66 100644 --- a/clientSide/cyberArkRoyal.ps1 +++ b/clientSide/cyberArkRoyal.ps1 @@ -140,6 +140,7 @@ $EffectivePassword$ if ([string]::isNullOrEmpty( $caUser )) { $caUser = $env:username } if ((!$groupBasedMode) -and $authPrompt) { $caCredentials = Get-Credential -UserName $caUser -Message "Please enter your CyberArk Username and Password" } + # prepare RoyalJSON response $response = @{ } $response.Objects = @() @@ -173,7 +174,7 @@ function Get-Safes() { $safeName = $safe.SafeName $safes[ $safeName ] = $safe } - if ($debugOn) { Write-Host $stopWatch.Elapsed + " wrote safes to HashTable" } + if ($debugOn) { Write-Host $stopWatch.Elapsed + " fetched safes from API" } return $safes } @@ -192,6 +193,8 @@ function Get-SafeGroups() { $safes[ $safeName ] = $group } } + if ($debugOn) { Write-Host $stopWatch.Elapsed + " fetched safes from groups" } + return $safes } @@ -367,45 +370,29 @@ if ($settings.allAccountsMode) { } elseif ($settings.groupBasedMode) { $safes = Get-SafeGroups - if ($debugOn) { Write-Host $stopWatch.Elapsed + " catched group based safes: $( $safes.Count )" } + if ($debugOn) { Write-Host $stopWatch.Elapsed + " fetched group based safes: $( $safes.Count )" } } else { Invoke-Logon if ($debugOn) { Write-Host $stopWatch.Elapsed + " login done" } $safes = Get-Safes - if ($debugOn) { Write-Host $stopWatch.Elapsed + " catched safes: $( $safes.Count )" } + if ($debugOn) { Write-Host $stopWatch.Elapsed + " fetched safes: $( $safes.Count )" } } # get the prepared data file and remove BOM (thanks to .NET, IIS) if necessary $jsonFileData = Invoke-WebRequest -Uri $dataUrl -Method GET -UseBasicParsing -ContentType 'application/json; charset=utf-8' -if ($debugOn) { Write-Host $stopWatch.Elapsed + " catched json file length: $( $jsonFileData.RawContentLength)" } -$safesAndAccountsList = $jsonFileData.Content | Foreach-Object { $_ -replace "\xEF\xBB\xBF", "" } | ConvertFrom-Json - -# sort list -switch ($settings.folderCreation) { - "safe.name" { $sortedSafesAndAccountsList = $safesAndAccountsList.psobject.properties | Sort-Object { $_.Value.safe.safename } } - "safe.name-description" { $sortedSafesAndAccountsList = $safesAndAccountsList.psobject.properties | Sort-Object { $_.Value.safe.safename } } - "safe.description" { $sortedSafesAndAccountsList = $safesAndAccountsList.psobject.properties | Sort-Object { $_.Value.safe.description } } - "safe.description-name" { $sortedSafesAndAccountsList = $safesAndAccountsList.psobject.properties | Sort-Object { $_.Value.safe.description } } - Default { $sortedSafesAndAccountsList = $safesAndAccountsList.psobject.properties | Sort-Object { $_.Value.safe.safename } } -} +if ($debugOn) { Write-Host $stopWatch.Elapsed + " fetched json file length: $( $jsonFileData.RawContentLength)" } -# SafesAndAccountsList into a hashtable with key = order -$safesAndAccountsTable = @{ } -foreach ($entry in $sortedSafesAndAccountsList) { - $safesAndAccountsTable[ $safesAndAccountsTable.Count ] = @($entry.Name, $entry.Value) -} -if ($debugOn) { Write-Host $stopWatch.Elapsed + " wrote and sorted safesAndAccounts to HashTable" } +$safesAndAccounts = $jsonFileData.Content | Foreach-Object { $_ -replace "\xEF\xBB\xBF", "" } | ConvertFrom-Json -foreach ($safeKey in $safesAndAccountsTable.getEnumerator() | Sort-Object Key) { - $safeAndAccounts = $safeKey.Value - +foreach ($safeAccount in $safesAndAccounts) { + # match safe or continue - if ( !$settings.allAccountsMode -and !$safes.ContainsKey( $safeAndAccounts.safe.SafeName ) ) { continue } + if ( !$settings.allAccountsMode -and !$safes.ContainsKey( $safeAccount.SafeName ) ) { continue } # apply safeFilter - if ($settings.safeFilter -and !([regex]::Match( $safeAndAccounts.safe.SafeName, $settings.safeFilterRegex ).Success )) { continue } + if ($settings.safeFilter -and !([regex]::Match( $safeAccount.SafeName, $settings.safeFilterRegex ).Success )) { continue } if ($settings.folderCreation -eq "none") { $objects = @() @@ -417,30 +404,27 @@ foreach ($safeKey in $safesAndAccountsTable.getEnumerator() | Sort-Object Key) { $folder.ColorFromParent = $true switch ($settings.folderCreation) { - "safe.name" { $folder.Name = $safeAndAccounts.safe.SafeName } - "safe.name-description" { $folder.Name = $safeAndAccounts.safe.SafeName + ' - ' + $safeAndAccounts.safe.Description } - "safe.description" { $folder.Name = $safeAndAccounts.safe.Description } - "safe.description-name" { $folder.Name = $safeAndAccounts.safe.Description + ' - ' + $safeAndAccounts.safe.SafeName } + "safe.name" { $folder.Name = $safeAccount.SafeName } + "safe.name-description" { $folder.Name = $safeAccount.SafeName + ' - ' + $safeAccount.Description } + "safe.description" { $folder.Name = $safeAccount.Description } + "safe.description-name" { $folder.Name = $safeAccount.Description + ' - ' + $safeAccount.SafeName } } } - # get accounts hashtable with key = ID - $accounts = @{ } - $safeAndAccounts.accounts.psobject.properties | ForEach-Object { $accounts[ $_.Name] = $_.Value } - if ($debugOn) { Write-Host $stopWatch.Elapsed + " wrote accounts from $( $safeAndAccounts.safe.SafeName) to HashTable" } - foreach ($accountKey in $accounts.Keys) { - $accountDetails = $accounts[ $accountKey] - $accountPlatform = $accountDetails.platformId + foreach ($account in $safeAccount.Accounts) { + + $accountPlatform = $account.platformId + if (!$platformMapping.ContainsKey( $accountPlatform)) { continue } - if ($settings.excludeAccounts.Contains( $accountDetails.userName)) { continue } + if ($settings.excludeAccounts.Contains( $account.userName)) { continue } if ($debugOn) { $debugNrAccounts++ } # create connections for every configured connection component - if ($null -eq $accountDetails.remoteMachinesAccess.remoteMachines) { - Add-Member -InputObject $accountDetails -NotePropertyName 'target' -NotePropertyValue $accountDetails.address + if ($null -eq $account.remoteMachines) { + Add-Member -InputObject $account -NotePropertyName 'target' -NotePropertyValue $account.address $royalPlatform = $platformMapping[ $accountPlatform] foreach ($connection in $royalPlatform.connections) { foreach ($component in $connection.components) { - $connectionEntry = Get-ConnectionEntry $accountDetails $royalPlatform $connection.Type $component + $connectionEntry = Get-ConnectionEntry $account $royalPlatform $connection.Type $component if ($settings.folderCreation -eq "none") { $objects += $connectionEntry } else { $folder.Objects += $connectionEntry } if ($debugOn) { $debugNrServerConnections++ } @@ -449,13 +433,13 @@ foreach ($safeKey in $safesAndAccountsTable.getEnumerator() | Sort-Object Key) { } # create connections for each remoteMachine and every configured connection component else { - $remoteMachines = $accountDetails.remoteMachinesAccess.remoteMachines.split(';', [System.StringSplitOptions]::RemoveEmptyEntries) | Sort-Object - foreach ($rmAddress in $remoteMachines) { - Add-Member -InputObject $accountDetails -NotePropertyName 'target' -NotePropertyValue $rmAddress -Force + $rmMachines = $account.remoteMachines.split(';', [System.StringSplitOptions]::RemoveEmptyEntries) | Sort-Object + foreach ($rmAddress in $rmMachines) { + Add-Member -InputObject $account -NotePropertyName 'target' -NotePropertyValue $rmAddress -Force $royalPlatform = $platformMapping[ $accountPlatform] foreach ($connection in $royalPlatform.connections) { foreach ($component in $connection.components) { - $connectionEntry = Get-ConnectionEntry $accountDetails $royalPlatform $connection.Type $component + $connectionEntry = Get-ConnectionEntry $account $royalPlatform $connection.Type $component if ($settings.folderCreation -eq "none") { $objects += $connectionEntry } else { $folder.Objects += $connectionEntry } if ($debugOn) { $debugNrServerConnections++ } @@ -477,7 +461,7 @@ foreach ($safeKey in $safesAndAccountsTable.getEnumerator() | Sort-Object Key) { $jsonResponse = $response | ConvertTo-Json -Depth 100 if ($debugOn) { - Write-Host $stopWatch.Elapsed + " got $( $jsonResponse.objects.Count) folders with $debugNrAccounts accounts and $debugNrServerConnections server connections" + Write-Host $stopWatch.Elapsed + " got $debugNrServerConnections server connections" Out-File -FilePath "data.json" -Encoding UTF8 -InputObject $jsonResponse } else { diff --git a/serverSide/cyberarkSafeAccountList.ps1 b/serverSide/cyberarkSafeAccountList.ps1 index 3356290..d57da2b 100644 --- a/serverSide/cyberarkSafeAccountList.ps1 +++ b/serverSide/cyberarkSafeAccountList.ps1 @@ -96,7 +96,6 @@ function Invoke-Request { return $response } - ######################################### # MAIN # ######################################### @@ -124,25 +123,30 @@ Write-Log -LogString "Retrieved $($safes.Count) CyberArk safes" if ($debugOn) { Write-Host $stopWatch.Elapsed + " catched safes: $($safes.Count)" } # get accounts from safe list -$safeAndAccountsList = @{ } +$safesAndAccounts = @() +$accountEntriesCount = 0 + foreach ($safe in $safes) { $accountURL = $pvwaUrl + '/api/Accounts?limit=1000&filter=safeName eq ' + $safe.SafeName $accountsResult = $(Invoke-Request -Uri $accountURL -Headers $header -Method Get).content | ConvertFrom-Json if ($null -ne $accountsResult.value -and $accountsResult.value.Length -gt 0) { - $safeAndAccountsList.Add( $safe.SafeName, @{ } ) - $safeAndAccountsList[$safe.SafeName].Add( "safe", $safe ) - $safeAndAccountsList[$safe.SafeName].Add( "accounts", @{ } ) + $safeEntry = @{ "SafeName" = $safe.SafeName; "Description" = $safe.Description; "Accounts" = @() } + foreach ($account in $accountsResult.value) { - $safeAndAccountsList[$safe.SafeName]["accounts"].Add( $account.id, $account ) + $accountEntry = @{ "userName" = $account.userName; "address" = $account.address ; "platformId" = $account.platformId; "remoteMachines" = $account.remoteMachinesAccess.remoteMachines } + $safeEntry.Accounts += $accountEntry + $accountEntriesCount++ } + + $safesAndAccounts += $safeEntry } } -Write-Log -LogString "Retrieved $($safeAndAccountsList.Count) CyberArk accounts from all safes" -if ($debugOn) { Write-Host $stopWatch.Elapsed + " catched safes accounts: $($safeAndAccountsList.Count)" } +Write-Log -LogString "Retrieved $accountEntriesCount CyberArk accounts" +if ($debugOn) { Write-Host $stopWatch.Elapsed + " catched safes accounts: $accountEntriesCount" } # check accounts list -if ($safeAndAccountsList.Count -gt 1) { - $results = $safeAndAccountsList | ConvertTo-Json -Depth 100 +if ($safesAndAccounts.Count -gt 1) { + $results = $safesAndAccounts | ConvertTo-Json -Depth 100 $filePathBak = $filePath + '.bak' From 604f6dedc2b20ca4a06bd76fe77ee0d2cbb2c55a Mon Sep 17 00:00:00 2001 From: Reto Schelbert Date: Fri, 25 Jun 2021 09:22:42 +0200 Subject: [PATCH 2/3] resizing Mode RyoalTS v6 --- clientSide/cyberArkRoyal.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clientSide/cyberArkRoyal.ps1 b/clientSide/cyberArkRoyal.ps1 index 39b5a66..c2dacd0 100644 --- a/clientSide/cyberArkRoyal.ps1 +++ b/clientSide/cyberArkRoyal.ps1 @@ -33,6 +33,7 @@ $settings = @" "folderCreation": "safe.name", "entryName": "named", "enableNLA": 0, + "rdpResizeMode": "", "excludeAccounts": ["guest"], "useWebPluginWin": "f008c2f0-5fb3-4c5e-a8eb-8072c1183088", "platformMappings": { @@ -221,7 +222,8 @@ function Get-ConnectionRDP($acc, $plat, $comp) { if ([string]::isNullOrEmpty( $plat.color )) { $entry.ColorFromParent = $true } else { $entry.color = $plat.color } if ([string]::isNullOrEmpty( $plat.replacePsm )) { $entry.ComputerName = $psmRdpAddress } else { $entry.ComputerName = $plat.replacePsm } - + if ([string]::isNullOrEmpty( $settings.rdpResizeMode )) { $entry.ResizeMode = 'SmartSizing' } else { $entry.ResizeMode = $settings.rdpResizeMode } + $entry.Username = $caUser if ($plat.drivesRedirection) { $entry.Properties.RedirectDrives = 'true' } if ($settings.enableNLA) { $entry.NLA = 'true' } else { $entry.NLA = 'false' } From 349f56f739adcbcccecd5396e2ad59b0c34edc90 Mon Sep 17 00:00:00 2001 From: Reto Schelbert Date: Fri, 25 Jun 2021 09:25:54 +0200 Subject: [PATCH 3/3] resizeMode readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d82df13..c1938b0 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,8 @@ Script settings in JSON format which can be defined within the script or taken f | useWebPluginWin | "", "f008c2f0-5fb3-4c5e-a8eb-8072c1183088" | If not empty then this RoyalTS Windows Plugin ID will be taken as "Browser Engine" for Web connection entries. Also possible to just use Default Settings in RoyalTS | | folderCreation | none, safe.name, safe.description, safe.name-description, safe.description-name | Will creates folder for all connection entries based on the provided naming scheme: **none** will create no folders, **safe.name** will create folder based on the safe name the accounts are in, **safe.description** from the safe description, **safe.name-description** from safe name + description and **safe.description-name** from safe description + safe name | | entryName | simple (DEFAULT), named, full | Connection Entry name which can be **simple** or just empty as default and only has the "address" as entry name, **named** as "username@address" and **full** as "target - username@address" for domain users | -| enableNLA | boolean 1 (true) or 0 (false) | Enables NLA in RDP connection entries | +| enableNLA | ScrollBars, SmartResize (default) | Sets the resize mode for RDP connection in RoyalTS (RyoalTS v6 default will do a Reconnect) | +| rdpResizeMode | boolean 1 (true) or 0 (false) | Enables NLA in RDP connection entries | | excludeAccounts | string array["user1","user2"] | Excludes these accounts / usernames from creating any connection entries | > In some specific RoyalTS versions there is an issue with "true" und "false" value, where RoyalTS did rewrite these keywords in capital letters. This becomes an invalid type in JSON format so we use 0 (false) or 1 (true) > instead of the keywords in our json settings