From 37c8833e71b79e9af5ea76bf61751654503b4584 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 04:44:23 -0800 Subject: [PATCH 01/21] Credential Storage Upgrade and Control API Token support --- ...nal Monitor for Automate - Recommended.sql | 8 +- Private/Get-CWAIKToken.ps1 | 23 +++ Public/Connect-AutomateAPI.ps1 | 2 +- Public/Connect-ControlAPI.ps1 | 105 ++++++---- Public/Get-ControlSessions.ps1 | 58 ++++-- .../Helpers/Get-CredentialsLocallyStored.ps1 | 88 ++++++--- .../Helpers/Set-CredentialsLocallyStored.ps1 | 98 +++++++--- Public/Invoke-ControlCommand2.ps1 | 179 ++++++++++++++++++ 8 files changed, 450 insertions(+), 111 deletions(-) create mode 100644 Private/Get-CWAIKToken.ps1 create mode 100644 Public/Invoke-ControlCommand2.ps1 diff --git a/Internal Monitor for Automate - Recommended.sql b/Internal Monitor for Automate - Recommended.sql index 9feabe2..716d008 100644 --- a/Internal Monitor for Automate - Recommended.sql +++ b/Internal Monitor for Automate - Recommended.sql @@ -1,16 +1,14 @@ ################## -# Monitor: GetControlSessionIDs* (254988) +# Monitor: GetControlSessionIDs* # Interval: 300 seconds # Alert Mode: Send Fail after Success # Alert Template: Default - Do Nothing # Alert Script: No script selected -# Ticket Category: No category selected # # Targeted Groups: # Global Monitor. No groups targeted. # -# Last Modified by dwhite@automate.apextmi.com -# Last Run - 2019-02-16 07:17:34 +# Last Modified by dwhite ################## INSERT INTO `agents` (`AgentID`, `Name`,`LocID`,`ClientID`,`ComputerID`,`DriveID`,`CheckAction`,`AlertAction`,`AlertMessage`,`ContactID`,`interval`, `Where`, `What`, `DataOut`,`Comparor`,`DataIn`,`LastScan`, `LastFailed`,`FailCount`,`IDField`,`AlertStyle`,`Changed`, `Last_Date`,`Last_User`, `ReportCategory`,`TicketCategory`,`Flags`, `GUID`,`AgentDefaultGUID`,`WarningCount`, `DeviceId`) -VALUES (NULL,'GetControlSessionIDs*',0,0,0,'0',0,1,'%NAME% %STATUS% on %CLIENTNAME%\\%COMPUTERNAME% at %LOCATIONNAME% for %FIELDNAME% result %RESULT%.!!!%NAME% %STATUS% on %CLIENTNAME%\\%COMPUTERNAME% at %LOCATIONNAME% for %FIELDNAME% result %RESULT%.',0,300,'plugin_screenconnect_scinstalled','IsSCInstalled','',1,'1',NOW(),'1979-01-01 01:01:01',0,'plugin_screenconnect_scinstalled.SessionGUID',0,0,'2019-02-16 07:17:34','dwhite@automate.apextmi.com',0,0,1,'f5c71af8-5752-4b1e-aa87-2b8cfe45b68d','',0,0); \ No newline at end of file +VALUES ((SELECT MAX(AgentID) FROM Agents WHERE GUID='f5c71af8-5752-4b1e-aa87-2b8cfe45b68d')),'GetControlSessionIDs*',0,0,0,'0',0,1,'%NAME% %STATUS% on %CLIENTNAME%\\%COMPUTERNAME% at %LOCATIONNAME% for %FIELDNAME% result %RESULT%.!!!%NAME% %STATUS% on %CLIENTNAME%\\%COMPUTERNAME% at %LOCATIONNAME% for %FIELDNAME% result %RESULT%.',0,300,'plugin_screenconnect_scinstalled','IsSCInstalled','',1,'1',NOW(),'1979-01-01 01:01:01',0,'plugin_screenconnect_scinstalled.SessionGUID',0,0,'2019-02-16 07:17:34','dwhite',0,0,1,'f5c71af8-5752-4b1e-aa87-2b8cfe45b68d','',0,0); diff --git a/Private/Get-CWAIKToken.ps1 b/Private/Get-CWAIKToken.ps1 new file mode 100644 index 0000000..e8812e5 --- /dev/null +++ b/Private/Get-CWAIKToken.ps1 @@ -0,0 +1,23 @@ +function Get-CWAIKToken { + param ( + [Parameter(Position=0)] + $APIKey = ([SecureString]$Script:ControlAPIKey) + ) + + If (!$APIKey) { + Throw "The API Key is not defined and must be provided" + Continue + } + + If ($APIKey.GetType() -match 'SecureString') { + $APIKey = $([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($APIKey))) + } + + $TimeStepSeconds = 600 + $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 + $epochsteps = [long]$((New-TimeSpan -Start $origin -End $(get-date).ToUniversalTime()).TotalSeconds/$TimeStepSeconds) + $barray=[System.BitConverter]::GetBytes($epochsteps); [array]::Reverse($barray) + $hmacsha = [System.Security.Cryptography.HMACSHA256]::new([Convert]::FromBase64String($APIKey)) + $Local:CWAIKToken = [Convert]::ToBase64String($hmacsha.ComputeHash($barray)) + Return $Local:CWAIKToken +} \ No newline at end of file diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index 226f81d..6f96e9b 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -126,7 +126,7 @@ Connect-AutomateAPI -Quiet $Script:CWACredentials = $Null $Script:CWACredentialsExpirationDate = $Null If ($AutomateCredentials) { - Throw "Attempt to authenticated to the Automate API has failed with error $_.Exception.Message" + Throw "Attempt to authenticate to the Automate API has failed with error $_.Exception.Message" Return } } diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index cc854f4..b288b94 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -6,8 +6,10 @@ function Connect-ControlAPI { Creates a Control hashtable in memory containing the server and username/password so that it can be used in other functions that connect to ConnectWise Control. Unfortunately the Control API does not support 2FA. .PARAMETER Server The address to your Control Server. Example 'https://control.rancorthebeast.com:8040' - .PARAMETER ControlCredentials + .PARAMETER Credentials Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass + .PARAMETER APIKey + Automate APIKey for Control Extension .PARAMETER Quiet Will not output any standard logging messages .PARAMETER TestCredentials @@ -23,19 +25,20 @@ function Connect-ControlAPI { All values will be prompted for one by one: Connect-ControlAPI All values needed to Automatically create appropriate output - Connect-ControlAPI -Server "https://control.rancorthebeast.com:8040" -ControlCredentials $CredentialsToPass + Connect-ControlAPI -Server "https://control.rancorthebeast.com:8040" -Credentials $CredentialsToPass #> [CmdletBinding(DefaultParameterSetName = 'refresh')] param ( [Parameter(ParameterSetName = 'credential', Mandatory = $False)] - [System.Management.Automation.PSCredential]$ControlCredentials, + [System.Management.Automation.PSCredential]$Credentials, [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] + [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] [String]$Server = $Script:ControlServer, -# [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] -# [String]$AuthorizationToken = ($Script:ControlAPICredentials), + [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] + [String]$APIKey = ($Script:ControlAPIKey), # [Parameter(ParameterSetName = 'credential', Mandatory = $False)] # [String]$TwoFactorToken, @@ -48,6 +51,7 @@ function Connect-ControlAPI { [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] + [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] [Switch]$Quiet ) @@ -67,55 +71,80 @@ function Connect-ControlAPI { Process { If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} - If ($SkipCheck -and $ControlCredentials) { + If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and $Null -ne $ControlCredentials) { $Script:ControlAPICredentials = $ControlCredentials $Script:ControlServer = $Server Return - } - If (!$AuthorizationToken) {$AuthorizationToken = $Script:ControlAPICredentials} - Do { - $ControlAPITestURI = ($Server + '/Services/PageService.ashx/GetHostSessionInfo') - If (!$Quiet) { - If (!$ControlCredentials -and ($Force -or !$AuthorizationToken)) { - $AuthorizationToken = $Null - $Username = Read-Host -Prompt "Please enter your Control Username" - $Password = Read-Host -Prompt "Please enter your Control Password" -AsSecureString - $ControlCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + } ElseIf ($PSCmdlet.ParameterSetName -eq 'apikey' -and $Null -ne $APIKey) { + If ($APIKey -notmatch '[a-f0-9]{100,}') { + [SecureString]$APIKey = ConvertTo-SecureString $APIKey -AsPlainText -Force + } + If (!$SkipCheck) { + # Retrieve Control Instance ID to verify APIKey + $RESTRequest = @{ + 'Method' = 'GET' + 'URI' = "$($Server)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/Service.ashx/GetInstanceID" + 'Headers' = @{'CWAIKToken' = (Get-CWAIKToken -APIKey $APIKey)} } - If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') { - $TwoFactorToken = Read-Host -Prompt "Please enter your 2FA Token" + Try { + $Null = Invoke-RestMethod @RESTRequest + } Catch { + $APIKey=$Null + Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" + Return } } + Return + } Else { + If (!$AuthorizationToken) {$AuthorizationToken = $Script:ControlAPICredentials} + Do { + $ControlAPITestURI = ($Server + '/Services/PageService.ashx/GetHostSessionInfo') + If (!$Quiet) { + If (!$ControlCredentials -and ($Force -or !$AuthorizationToken)) { + $AuthorizationToken = $Null + $Username = Read-Host -Prompt "Please enter your Control Username" + $Password = Read-Host -Prompt "Please enter your Control Password" -AsSecureString + $ControlCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + } + If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') { + $TwoFactorToken = Read-Host -Prompt "Please enter your 2FA Token" + } + } - #Invoke the REST Method - Write-Debug "Submitting Request to $ControlAPITestURI" - If (!$AuthorizationToken) {$AuthorizationToken=$ControlCredentials} - Try { - $ControlAPITokenResult = Invoke-RestMethod -Uri $ControlAPITestURI -Method Get -Credential $AuthorizationToken - } - Catch { - $Script:ControlAPICredentials = $Null - If ($ControlCredentials) { - Throw "Unable to connect to Control. Server or Control Credentials are wrong. This module does not support 2FA for Control Users" - Return + #Invoke the REST Method + Write-Debug "Submitting Request to $ControlAPITestURI" + If (!$AuthorizationToken) {$AuthorizationToken=$ControlCredentials} + Try { + $ControlAPITokenResult = Invoke-RestMethod -Uri $ControlAPITestURI -Method Get -Credential $AuthorizationToken } - } - $AuthorizationResult=$ControlAPITokenResult.Version - $TwoFactorNeeded=$ControlAPITokenResult.IsTwoFactorRequired - } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationResult) -or - ($TwoFactorNeeded -ne $True -and $ControlCredentials) -or - ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') - ) + Catch { + $Script:ControlAPICredentials = $Null + If ($ControlCredentials) { + Throw "Unable to connect to Control. Server or Control Credentials are wrong. This module does not support 2FA for Control Users" + Return + } + } + $AuthorizationResult=$ControlAPITokenResult.Version + $TwoFactorNeeded=$ControlAPITokenResult.IsTwoFactorRequired + } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationResult) -or + ($TwoFactorNeeded -ne $True -and $ControlCredentials) -or + ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') + ) + } } End { - If ([string]::IsNullOrEmpty($AuthorizationToken)) { + If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey) -or ($PSCmdlet.ParameterSetName -ne 'apikey' -and [string]::IsNullOrEmpty($AuthorizationToken))) { Throw "Unable to get Access Token. Either the credentials your entered are incorrect or you did not pass a valid two factor token" If ($Quiet) { Return $False } } Else { - $Script:ControlAPICredentials = $AuthorizationToken + If ($PSCmdlet.ParameterSetName -eq 'apikey') { + $Script:ControlAPIKey = $APIKey + } Else { + $Script:ControlAPICredentials = $AuthorizationToken + } $Script:ControlServer = $Server If (!$Quiet) { Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Control API. Server version is $($ControlAPITokenResult.ProductVersion)" diff --git a/Public/Get-ControlSessions.ps1 b/Public/Get-ControlSessions.ps1 index c181a99..d4905f1 100644 --- a/Public/Get-ControlSessions.ps1 +++ b/Public/Get-ControlSessions.ps1 @@ -22,31 +22,49 @@ function Get-ControlSessions { } end { - $Body='["SessionConnectionEvent",["SessionID","EventType"],["LastTime"],"SessionConnectionProcessType=\u0027Guest\u0027 AND (EventType = \u0027Connected\u0027 OR EventType = \u0027Disconnected\u0027)",20000]' - $SCData = Invoke-RestMethod -Uri "$($Script:ControlServer)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" -Method POST -Credential $($Script:ControlAPICredentials) -ContentType "application/json" -Body $Body + $Body=ConvertTo-Json @("SessionConnectionEvent",@("SessionID","EventType"),@("LastTime"),"SessionConnectionProcessType=""Guest"" AND (EventType = ""Connected"" OR EventType = ""Disconnected"")", "", 20000) -Compress + $RESTRequest = @{ + 'URI' = "${Script:ControlServer}/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } + $SCConnected = @{}; - $AllData = $SCData.Items.GetEnumerator() | select-object @{Name='SessionID'; Expression={$_[0]}},@{Name='Event'; Expression={$_[1]}},@{Name='Date'; Expression={$_[2]}} | sort-Object SessionID,Event -Descending; - $AllData | ForEach-Object { - if (!($_.SessionID -and $_.Date -and $_.Event)) {'WARNING.. Weird data found.'; $_} - if ($_.Event -like 'Disconnected') { - $SCConnected.Add($_.SessionID,$_.Date) - } else { - if ($_.Date -ge $SCConnected[$_.SessionID]) { - if ($SCConnected.ContainsKey($_.SessionID)) { - $SCConnected[$_.SessionID]=$True + Try { + $SCData = Invoke-RestMethod @RESTRequest + If ($SCData.FieldNames -contains 'SessionID' -and $SCData.FieldNames -contains 'EventType' -and $SCData.FieldNames -contains 'LastTime') { + $AllData = $SCData.Items.GetEnumerator() | select-object @{Name='SessionID'; Expression={$_[0]}},@{Name='Event'; Expression={$_[1]}},@{Name='Date'; Expression={$_[2]}} | sort-Object SessionID,Event -Descending; + $AllData | ForEach-Object { + if ($_.Event -like 'Disconnected') { + $SCConnected.Add($_.SessionID,$_.Date) } else { - $SCConnected.Add($_.SessionID,$True) - } - } else { - if ($SCConnected.ContainsKey($_.SessionID)) { - $SCConnected[$_.SessionID]=$False - } else { - $SCConnected.Add($_.SessionID,$False) + if ($_.Date -ge $SCConnected[$_.SessionID]) { + if ($SCConnected.ContainsKey($_.SessionID)) { + $SCConnected[$_.SessionID]=$True + } else { + $SCConnected.Add($_.SessionID,$True) + } + } else { + if ($SCConnected.ContainsKey($_.SessionID)) { + $SCConnected[$_.SessionID]=$False + } else { + $SCConnected.Add($_.SessionID,$False) + } + } } } + Foreach ($sessid IN $( ($SCConnected.GetEnumerator() | Where-Object {$_.Value -ne $True -and $_.Value -ne $False}).Key)) {$SCConnected[$sessid]=$False} + } Else { + Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" + Return } - } - Foreach ($sessid IN $( ($SCConnected.GetEnumerator() | Where-Object {$_.Value -ne $True -and $_.Value -ne $False}).Key)) {$SCConnected[$sessid]=$False} + } Catch { } return $SCConnected } } diff --git a/Public/Helpers/Get-CredentialsLocallyStored.ps1 b/Public/Helpers/Get-CredentialsLocallyStored.ps1 index c81655d..c666f2e 100644 --- a/Public/Helpers/Get-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Get-CredentialsLocallyStored.ps1 @@ -7,7 +7,7 @@ function Get-CredentialsLocallyStored { [Parameter(ParameterSetName = 'Control')] [switch]$Control, - [Parameter(ParameterSetName = 'Custom')] + [Parameter(ParameterSetName = 'Custom',Mandatory=$True)] [string]$CredentialPath, [Parameter(ParameterSetName = 'Automate')] @@ -18,40 +18,78 @@ function Get-CredentialsLocallyStored { if ($Automate) { if (-not (Test-Path "$($CredentialDirectory)Automate - Credentials.txt")) { throw [System.IO.FileNotFoundException] "Automate Credentials not found at $($CredentialDirectory)Automate - Credentials.txt"} - $LocalAutomateCredentials = Get-Content "$($CredentialDirectory)Automate - Credentials.txt" - $LocalAutomateUsername = $LocalAutomateCredentials[0] - $LocalAutomatePassword = $LocalAutomateCredentials[1] | ConvertTo-SecureString - $LocalAutomateServer = $LocalAutomateCredentials[2] - $LocalAutomateCredentialsObject = New-Object System.Management.Automation.PSCredential -ArgumentList $LocalAutomateUsername, $LocalAutomatePassword - try { - Connect-AutomateAPI -AutomateCredentials $LocalAutomateCredentialsObject -Server $LocalAutomateServer - } - catch { - Write-Error $_ + $StoreVariables = @( + @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, + @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, + @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, + @{'Name' = 'CWAToken'; 'Scope' = 'Script'}, + @{'Name' = 'CWATokenExpirationDate'; 'Scope' = 'Script'} + ) + $StoreBlock = Get-Content "$($CredentialDirectory)Automate - Credentials.txt" | ConvertFrom-Json + Foreach ($SaveVar in $StoreVariables) { + If (!($StoreBlock.$($SaveVar.Name))) {Continue} + If ($SaveVar.Name -match 'Credential') { + $Null = Set-Variable @SaveVar -Value $(New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.$($SaveVar.Name).Username), $($StoreBlock.$($SaveVar.Name).Password|ConvertTo-SecureString)) + } ElseIf ($SaveVar.Name -match 'Key') { + $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)|ConvertTo-SecureString) + } Else { + $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)) + } } + +# $LocalAutomateUsername = $LocalAutomateCredentials[0] +# $LocalAutomatePassword = $LocalAutomateCredentials[1] | ConvertTo-SecureString +# $LocalAutomateServer = $LocalAutomateCredentials[2] +# $LocalAutomateCredentialsObject = New-Object System.Management.Automation.PSCredential -ArgumentList $LocalAutomateUsername, $LocalAutomatePassword +# try { +# Connect-AutomateAPI -AutomateCredentials $LocalAutomateCredentialsObject -Server $LocalAutomateServer +# } +# catch { +# Write-Error $_ +# } } if ($Control) { if (-not (Test-Path "$($CredentialDirectory)Control - Credentials.txt")) { throw [System.IO.FileNotFoundException] "Control Credentials not found at $($CredentialDirectory)Control - Credentials.txt"} - $LocalControlCredentials = Get-Content "$($CredentialDirectory)Control - Credentials.txt" - $LocalControlUsername = $LocalControlCredentials[0] - $LocalControlPassword = $LocalControlCredentials[1] | ConvertTo-SecureString - $LocalControlServer = $LocalControlCredentials[2] - $LocalControlCredentialsObject = New-Object System.Management.Automation.PSCredential -ArgumentList $LocalControlUsername, $LocalControlPassword - try { - Connect-ControlAPI -ControlCredentials $LocalControlCredentialsObject -Server $LocalControlServer -ErrorAction Stop - } - catch { - Write-Error "Unable to store or retrieve Control credentials with error $_.Exception.Message" + $StoreVariables = @( + @{'Name' = 'ControlAPICredentials'; 'Scope' = 'Script'}, + @{'Name' = 'ControlServer'; 'Scope' = 'Script'}, + @{'Name' = 'ControlAPIKey'; 'Scope' = 'Script'} + ) + + $StoreBlock = Get-Content "$($CredentialDirectory)Control - Credentials.txt" | ConvertFrom-Json + Foreach ($SaveVar in $StoreVariables) { + If (!($StoreBlock.$($SaveVar.Name))) {Continue} + If ($SaveVar.Name -match 'Credential') { + $Null = Set-Variable @SaveVar -Value $(New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.$($SaveVar.Name).Username), $($StoreBlock.$($SaveVar.Name).Password|ConvertTo-SecureString)) + } ElseIf ($SaveVar.Name -match 'Key') { + $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)|ConvertTo-SecureString) + } Else { + $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)) + } } + +# $LocalControlUsername = $LocalControlCredentials[0] +# $LocalControlPassword = $LocalControlCredentials[1] | ConvertTo-SecureString +# $LocalControlServer = $LocalControlCredentials[2] +# $LocalControlCredentialsObject = New-Object System.Management.Automation.PSCredential -ArgumentList $LocalControlUsername, $LocalControlPassword +# try { +# Connect-ControlAPI -ControlCredentials $LocalControlCredentialsObject -Server $LocalControlServer -ErrorAction Stop +# } +# catch { +# Write-Error "Unable to store or retrieve Control credentials with error $_.Exception.Message" +# } } if ($Custom) { if (-not (Test-Path "$($CredentialPath)")) { throw [System.IO.FileNotFoundException] "Credentials not found at $($CredentialPath)"} - $CustomCredentials = Get-Content $CredentialPath - $CustomUsername = $CustomCredentials[0] - $CustomPassword = $CustomCredentials[1] | ConvertTo-SecureString - $CustomCredentialObject = New-Object System.Management.Automation.PSCredential -ArgumentList $CustomUsername, $CustomPassword + $StoreBlock = Get-Content $CredentialPath | ConvertFrom-Json + If (!($StoreBlock.CustomCredentials)) {Continue} + +# $CustomCredentials = Get-Content $CredentialPath +# $CustomUsername = $CustomCredentials[0] +# $CustomPassword = $CustomCredentials[1] | ConvertTo-SecureString + $CustomCredentialObject = New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.CustomCredentials.Username), $($StoreBlock.CustomCredentials.Password|ConvertTo-SecureString) return $CustomCredentialObject } diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index 55aa604..9b11a1d 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -41,7 +41,11 @@ function Set-CredentialsLocallyStored { [Parameter(ParameterSetName = 'Automate')] [Parameter(ParameterSetName = 'Control')] - [Parameter(ParameterSetName = "Custom",Mandatory=$True)] + [switch]$Save, + + [Parameter(ParameterSetName = 'Automate')] + [Parameter(ParameterSetName = 'Control')] + [Parameter(ParameterSetName = "Custom",Mandatory=$True)] [string]$CredentialDirectory = "$($env:USERPROFILE)\AutomateAPI\" ) @@ -56,42 +60,92 @@ function Set-CredentialsLocallyStored { if ($Automate) { + $StoreVariables = @( + @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, + @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, + @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, + @{'Name' = 'CWAToken'; 'Scope' = 'Script'}, + @{'Name' = 'CWATokenExpirationDate'; 'Scope' = 'Script'} + ) + $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)Automate - Credentials.txt" - $TempAutomateServer = Read-Host -Prompt "Please enter your Automate Server address, without the HTTPS, IE: rancor.hostedrmm.com" - $TempAutomateUsername = Read-Host -Prompt "Please enter your Automate Username" - $TempAutomatePassword = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString - $TempAutomatePassword = $TempAutomatePassword | ConvertFrom-SecureString - - Set-Content "$CredentialPath" $TempAutomateUsername -Force - Add-Content "$CredentialPath" $TempAutomatePassword - Add-Content "$CredentialPath" $TempAutomateServer + If ($Save) { + Foreach ($SaveVar in $StoreVariables) { + If (!(Get-Variable @SaveVar)) {Continue} + If ($SaveVar.Name -match 'Credential') { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} + } ElseIf ($SaveVar.Name -match 'Key') { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly|ConvertFrom-SecureString) + } Else { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) + } + } + } Else { + $TempAutomateServer = Read-Host -Prompt "Please enter your Automate Server address, without the HTTPS, IE: rancor.hostedrmm.com" + $TempAutomateUsername = Read-Host -Prompt "Please enter your Automate Username" + $TempAutomatePassword = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString +# $TempAutomatePassword = $TempAutomatePassword | ConvertFrom-SecureString + $Null = $StoreBlock | Add-Member -NotePropertyName 'CWAServer' -NotePropertyValue $TempAutomateServer + $Null = $StoreBlock | Add-Member -NotePropertyName 'CWACredentials' -NotePropertyValue @{'UserName'=$TempAutomateUsername; 'Password'=($TempAutomatePassword | ConvertFrom-SecureString)} + } + + $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline + +# Set-Content "$CredentialPath" $TempAutomateUsername -Force +# Add-Content "$CredentialPath" $TempAutomatePassword +# Add-Content "$CredentialPath" $TempAutomateServer Write-Output "Automate Credentials Set" } if ($Control) { + $StoreVariables = @( + @{'Name' = 'ControlAPICredentials'; 'Scope' = 'Script'}, + @{'Name' = 'ControlServer'; 'Scope' = 'Script'}, + @{'Name' = 'ControlAPIKey'; 'Scope' = 'Script'} + ) + $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)Control - Credentials.txt" - $TempControlServer = Read-Host -Prompt "Please enter your Control Server address, the full URL. IE https://control.rancorthebeast.com:8040" - $TempControlUsername = Read-Host -Prompt "Please enter your Control Username" - $TempControlPassword = Read-Host -Prompt "Please enter your Control Password" -AsSecureString - $TempControlPassword = $TempControlPassword | ConvertFrom-SecureString + If ($Save) { + Foreach ($SaveVar in $StoreVariables) { + If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} + If ($SaveVar.Name -match 'Credential') { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} + } ElseIf ($SaveVar.Name -match 'Key') { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly|ConvertFrom-SecureString) + } Else { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) + } + } + } Else { + $TempControlServer = Read-Host -Prompt "Please enter your Control Server address, the full URL. IE https://control.rancorthebeast.com:8040" + $TempControlUsername = Read-Host -Prompt "Please enter your Control Username" + $TempControlPassword = Read-Host -Prompt "Please enter your Control Password" -AsSecureString +# $TempControlPassword = $TempControlPassword | ConvertFrom-SecureString + $Null = $StoreBlock | Add-Member -NotePropertyName 'ControlServer' -NotePropertyValue $TempControlServer + $Null = $StoreBlock | Add-Member -NotePropertyName 'ControlAPICredentials' -NotePropertyValue @{'UserName'=$TempControlUsername; 'Password'=($TempControlPassword | ConvertFrom-SecureString)} + } - Set-Content "$CredentialPath" $TempControlUsername -Force - Add-Content "$CredentialPath" $TempControlPassword - Add-Content "$CredentialPath" $TempControlServer + $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline +# Set-Content "$CredentialPath" $TempControlUsername -Force +# Add-Content "$CredentialPath" $TempControlPassword +# Add-Content "$CredentialPath" $TempControlServer Write-Output "Control Credentials Set" } if ($Custom) { + $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)\$($CredentialDisplayName).txt" $CustomCredentials = Get-Credential -Message "Please enter the Custom Username and Password to store" - $CustomUsername = $CustomCredentials.UserName - $CustomPasswordSecureString = $CustomCredentials.Password - $CustomPassword = $CustomPasswordSecureString | ConvertFrom-SecureString - - Set-Content "$CredentialPath" $CustomUsername -Force - Add-Content "$CredentialPath" $CustomPassword +# $CustomUsername = $CustomCredentials.UserName +# $CustomPasswordSecureString = $CustomCredentials.Password +# $CustomPassword = $CustomPasswordSecureString | ConvertFrom-SecureString + $Null = $StoreBlock | Add-Member -NotePropertyName 'CustomCredentials' -NotePropertyValue @{'UserName'=$CustomCredentials.Password; 'Password'=($CustomCredentials.Password | ConvertFrom-SecureString)} + + $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline + # Set-Content "$CredentialPath" $CustomUsername -Force + # Add-Content "$CredentialPath" $CustomPassword Write-Output "Custom Credentials Set" } diff --git a/Public/Invoke-ControlCommand2.ps1 b/Public/Invoke-ControlCommand2.ps1 new file mode 100644 index 0000000..4508c26 --- /dev/null +++ b/Public/Invoke-ControlCommand2.ps1 @@ -0,0 +1,179 @@ +function Invoke-ControlCommand { + <# + .SYNOPSIS + Will issue a command against a given machine and return the results. + .DESCRIPTION + Will issue a command against a given machine and return the results. + .PARAMETER GUID + The GUID identifier for the machine you wish to connect to. + You can retrieve session info with the 'Get-CWCSessions' commandlet + .PARAMETER Command + The command you wish to issue to the machine. + .PARAMETER TimeOut + The amount of time in milliseconds that a command can execute. The default is 10000 milliseconds. + .PARAMETER PowerShell + Issues the command in a powershell session. + .PARAMETER Group + Name of session group to use. + .OUTPUTS + The output of the Command provided. + .NOTES + Version: 1.0 + Author: Chris Taylor + Modified By: Gavin Stone + Creation Date: 1/20/2016 + Purpose/Change: Initial script development + .EXAMPLE + Invoke-ControlCommand -GUID $GUID -Command 'hostname' + Will return the hostname of the machine. + .EXAMPLE + Invoke-ControlCommand -GUID $GUID -User $User -Password $Password -TimeOut 120000 -Command 'iwr -UseBasicParsing "https://bit.ly/ltposh" | iex; Restart-LTService' -PowerShell + Will restart the Automate agent on the target machine. + #> + [CmdletBinding()] + param ( + [string]$Server = $Script:ControlServer, + [System.Management.Automation.PSCredential]$Credentials = $Script:ControlAPICredentials, + [Parameter(Mandatory=$True)] + [guid]$GUID, + [string]$Command, + [int]$TimeOut = 10000, + [switch]$PowerShell, +# [string]$Group = "All Machines", + [int]$MaxLength = 5000 + ) + + $Server = $Server -replace '/$','' + If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} + $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 + + # Format command + $FormattedCommand = @() + if ($Powershell) { + $FormattedCommand += '#!ps' + } + $FormattedCommand += "#timeout=$TimeOut" + $FormattedCommand += "#maxlength=$MaxLength" + $FormattedCommand += $Command + $FormattedCommand = $FormattedCommand | Out-String + + $SessionEventType = 44 + $Body = ConvertTo-Json @("",@($GUID),$SessionEventType,$FormattedCommand) -Compress + Write-Verbose $Body + + $RESTRequest = @{ + 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7//ReplicaService.ashx/PageAddEventToSessions" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } + + # Issue command + try { + $null = Invoke-RestMethod @RESTRequest + } + catch { + Write-Error "$(($_.ErrorDetails | ConvertFrom-Json).message)" + return + } + + #Get the timestamp for the Queued command. + $Body=ConvertTo-Json @("SessionEvent",@("SessionID"),@("LastTime"),"sessionid='$GUID' AND EventType='QueuedCommand'","",10) -Compress + $RESTRequest = @{ + 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } + + # Get Session + Write-Verbose $Body + try { + $SessionDetails = Invoke-RestMethod @RESTRequest + + $FNames=$SessionDetails.FieldNames + $SCRecords=($SessionDetails.Items | ForEach-Object { + $x=$_ + $SCEventRecord = [pscustomobject]@{} + for($i=0; $i -lt $FNames.Length; $i++){ + $Null = $SCEventRecord | Add-Member -NotePropertyName $FNames[$i] -NotePropertyValue $x[$i] + } + $SCEventRecord + }) + $EventDate=(Get-Date ($SCRecords | Select-Object -Expand LastTime) -UFormat "%Y-%m-%d %T") + } + catch { + Write-Error $($_.Exception.Message) + return + } + + #Get time command was executed +# $epoch = $((New-TimeSpan -Start $(Get-Date -Date "01/01/1970") -End $(Get-Date)).TotalSeconds) +# $ExecuteTime = $epoch - ((($SessionDetails.events | Where-Object {$_.EventType -eq 44})[-1]).Time /1000) +# $ExecuteDate = $origin.AddSeconds($ExecuteTime) + + # Look for results of command - We SHOULD be getting the SessionConnectionEvent data. + $Body=ConvertTo-Json @("SessionConnectionEvent",@(""),@("SessionID","Time","Data"),"SessionID='$GUID' AND EventType='RanCommand' AND Time>='$EventDate'","",100) -Compress +# $Body=ConvertTo-Json @("SessionConnectionEvent",@(""),@("SessionID","Time","Data"),"SessionID=""$GUID""","",100) -Compress +# $Body=ConvertTo-Json @("SessionEvent",@("SessionID"),@("SessionID","Time"),"","",100) -Compress +# $Body=ConvertTo-Json @("SessionEvent",@("SessionID","Time","Data"),@(""),'SessionID="19d61321-9c2e-42e5-addc-fe416d1e88be"',"",10) -Compress + $RESTRequest = @{ + 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } + + $Looking = $True + $TimeOutDateTime = (Get-Date).AddMilliseconds($TimeOut) + while ($Looking) { + try { + $SessionDetails = Invoke-RestMethod @RESTRequest + } + catch { + Write-Error $($_.Exception.Message) + return + } + + $ConnectionsWithData = @() + Foreach ($Connection in $SessionDetails.connections) { + $ConnectionsWithData += $Connection | Where-Object {$_.Events.EventType -eq 70} + } + + $Events = ($ConnectionsWithData.events | Where-Object {$_.EventType -eq 70 -and $_.Time}) + foreach ($Event in $Events) { + $epoch = $((New-TimeSpan -Start $(Get-Date -Date "01/01/1970") -End $(Get-Date)).TotalSeconds) + $CheckTime = $epoch - ($Event.Time /1000) + $CheckDate = $origin.AddSeconds($CheckTime) + if ($CheckDate -gt $ExecuteDate) { + $Looking = $False + $Output = $Event.Data -split '[\r\n]' | Where-Object {$_} + if(!$PowerShell){ + $Output = $Output | Select-Object -skip 1 + } + return $Output + } + } + + Start-Sleep -Seconds 1 + if ($(Get-Date) -gt $TimeOutDateTime.AddSeconds(1)) { + $Looking = $False + $Output = "Command timed out when sent to Agent" + } + } +} \ No newline at end of file From 11369662ee3c78136f7547e90eaaab1fb5e63e4e Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 05:43:43 -0800 Subject: [PATCH 02/21] Credential Storage updates --- Public/Connect-AutomateAPI.ps1 | 11 ++++++++--- Public/Get-AutomateAPIGeneric.ps1 | 4 ++-- Public/Get-AutomateControlInfo.ps1 | 2 +- Public/Helpers/Get-CredentialsLocallyStored.ps1 | 1 + Public/Helpers/Set-CredentialsLocallyStored.ps1 | 3 ++- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index 6f96e9b..bd65135 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -44,7 +44,7 @@ Connect-AutomateAPI -Quiet [String]$Server = $Script:CWAServer, [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] - [String]$AuthorizationToken = ($Script:CWACredentials.Authorization -replace 'Bearer ',''), + [String]$AuthorizationToken = ($Script:CWAToken.Authorization -replace 'Bearer ',''), [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] [Switch]$SkipCheck, @@ -84,7 +84,7 @@ Connect-AutomateAPI -Quiet $Null = $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") Write-Debug "Setting Credentials to $($AutomateToken.Authorization)" $Script:CWAServer = ("https://" + $Server) - $Script:CWACredentials = $AutomateToken + $Script:CWAToken = $AutomateToken Return } Do { @@ -123,6 +123,8 @@ Connect-AutomateAPI -Quiet $AutomateAPITokenResult = Invoke-RestMethod -Method Post -Uri $AutomateAPIURI -Body $PostBody -ContentType "application/json" } Catch { + $Script:CWAToken = $Null + $Script:CWATokenKey = $Null $Script:CWACredentials = $Null $Script:CWACredentialsExpirationDate = $Null If ($AutomateCredentials) { @@ -154,8 +156,11 @@ Connect-AutomateAPI -Quiet Write-Debug "Setting Credentials to $($AutomateToken.Authorization)" #Create Script Variables for this session in order to use the token $Script:CWAServer = ("https://" + $Server) - $Script:CWACredentials = $AutomateToken + $Script:CWACredentials = $AutomateCredentials + $Script:CWATokenKey = ConvertTo-SecureString $AutomateAPITokenResult.AccessToken -AsPlainText -Force + $Script:CWAToken = $AutomateToken $Script:CWACredentialsExpirationDate = $AutomateAPITokenResult.ExpirationDate + $Script:CWATokenResult = $AutomateAPITokenResult If (!$Quiet) { Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Automate REST API. Token will expire at $($AutomateAPITokenResult.ExpirationDate)" diff --git a/Public/Get-AutomateAPIGeneric.ps1 b/Public/Get-AutomateAPIGeneric.ps1 index c376e06..0441b3d 100644 --- a/Public/Get-AutomateAPIGeneric.ps1 +++ b/Public/Get-AutomateAPIGeneric.ps1 @@ -127,7 +127,7 @@ function Get-AutomateAPIGeneric { [int]$i += 1 $URLNew = "$($url)?page=$($i)" try { - $return = Invoke-RestMethod -Uri $URLNew -Headers $script:CWACredentials -ContentType "application/json" -Body $Body + $return = Invoke-RestMethod -Uri $URLNew -Headers $script:CWAToken -ContentType "application/json" -Body $Body } catch { Write-Error "Failed to perform Invoke-RestMethod to Automate API with error $_.Exception.Message" @@ -143,7 +143,7 @@ function Get-AutomateAPIGeneric { [System.Collections.ArrayList]$ReturnedResults $URLNew = "$($url)?page=$($Page)" try { - $return = Invoke-RestMethod -Uri $URLNew -Headers $script:CWACredentials -ContentType "application/json" -Body $Body + $return = Invoke-RestMethod -Uri $URLNew -Headers $script:CWAToken -ContentType "application/json" -Body $Body } catch { Write-Error "Failed to perform Invoke-RestMethod to Automate API with error $_.Exception.Message" diff --git a/Public/Get-AutomateControlInfo.ps1 b/Public/Get-AutomateControlInfo.ps1 index c7eca66..2ff783a 100644 --- a/Public/Get-AutomateControlInfo.ps1 +++ b/Public/Get-AutomateControlInfo.ps1 @@ -63,7 +63,7 @@ Get-AutomateControlInfo -ComputerId 123 } $url = ($Script:CWAServer + "/cwa/api/v1/extensionactions/control/$($Computer.ID)") Try { - $Result = Invoke-RestMethod -Uri $url -Headers $script:CWACredentials -ContentType "application/json" + $Result = Invoke-RestMethod -Uri $url -Headers $script:CWAToken -ContentType "application/json" $ResultMatch=$Result|select-string -Pattern '^(https?://[^?]*)\??(.*)' -AllMatches If ($ResultMatch.Matches) { diff --git a/Public/Helpers/Get-CredentialsLocallyStored.ps1 b/Public/Helpers/Get-CredentialsLocallyStored.ps1 index c666f2e..33119f5 100644 --- a/Public/Helpers/Get-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Get-CredentialsLocallyStored.ps1 @@ -23,6 +23,7 @@ function Get-CredentialsLocallyStored { @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, @{'Name' = 'CWAToken'; 'Scope' = 'Script'}, + @{'Name' = 'CWATokenResult'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenExpirationDate'; 'Scope' = 'Script'} ) $StoreBlock = Get-Content "$($CredentialDirectory)Automate - Credentials.txt" | ConvertFrom-Json diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index 9b11a1d..84f6237 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -65,6 +65,7 @@ function Set-CredentialsLocallyStored { @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, @{'Name' = 'CWAToken'; 'Scope' = 'Script'}, + @{'Name' = 'CWATokenResult'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenExpirationDate'; 'Scope' = 'Script'} ) $StoreBlock = [pscustomobject]@{} @@ -72,7 +73,7 @@ function Set-CredentialsLocallyStored { If ($Save) { Foreach ($SaveVar in $StoreVariables) { - If (!(Get-Variable @SaveVar)) {Continue} + If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} If ($SaveVar.Name -match 'Credential') { $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} } ElseIf ($SaveVar.Name -match 'Key') { From 185151332849310a08637461c8a808924021e54d Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 06:01:13 -0800 Subject: [PATCH 03/21] Securing credential information --- Public/Connect-AutomateAPI.ps1 | 4 ++-- Public/Helpers/Get-CredentialsLocallyStored.ps1 | 10 +++++++--- Public/Helpers/Set-CredentialsLocallyStored.ps1 | 4 +--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index bd65135..043f25b 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -159,8 +159,8 @@ Connect-AutomateAPI -Quiet $Script:CWACredentials = $AutomateCredentials $Script:CWATokenKey = ConvertTo-SecureString $AutomateAPITokenResult.AccessToken -AsPlainText -Force $Script:CWAToken = $AutomateToken - $Script:CWACredentialsExpirationDate = $AutomateAPITokenResult.ExpirationDate - $Script:CWATokenResult = $AutomateAPITokenResult + $AutomateAPITokenResult.PSObject.properties.remove('AccessToken') + $Script:CWATokenInfo = $AutomateAPITokenResult If (!$Quiet) { Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Automate REST API. Token will expire at $($AutomateAPITokenResult.ExpirationDate)" diff --git a/Public/Helpers/Get-CredentialsLocallyStored.ps1 b/Public/Helpers/Get-CredentialsLocallyStored.ps1 index 33119f5..a1cffeb 100644 --- a/Public/Helpers/Get-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Get-CredentialsLocallyStored.ps1 @@ -22,9 +22,7 @@ function Get-CredentialsLocallyStored { @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, - @{'Name' = 'CWAToken'; 'Scope' = 'Script'}, - @{'Name' = 'CWATokenResult'; 'Scope' = 'Script'}, - @{'Name' = 'CWATokenExpirationDate'; 'Scope' = 'Script'} + @{'Name' = 'CWATokenInfo'; 'Scope' = 'Script'} ) $StoreBlock = Get-Content "$($CredentialDirectory)Automate - Credentials.txt" | ConvertFrom-Json Foreach ($SaveVar in $StoreVariables) { @@ -37,6 +35,12 @@ function Get-CredentialsLocallyStored { $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)) } } + If ($Script:CWATokenKey -and $Script:CWATokenKey.GetType() -match 'SecureString') { + $AuthorizationToken = $([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Script:CWATokenKey))) + $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") + $Script:CWAToken = $AutomateToken + } # $LocalAutomateUsername = $LocalAutomateCredentials[0] # $LocalAutomatePassword = $LocalAutomateCredentials[1] | ConvertTo-SecureString diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index 84f6237..a86bd8a 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -64,9 +64,7 @@ function Set-CredentialsLocallyStored { @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, - @{'Name' = 'CWAToken'; 'Scope' = 'Script'}, - @{'Name' = 'CWATokenResult'; 'Scope' = 'Script'}, - @{'Name' = 'CWATokenExpirationDate'; 'Scope' = 'Script'} + @{'Name' = 'CWATokenInfo'; 'Scope' = 'Script'} ) $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)Automate - Credentials.txt" From 0af3f5dd05dcd189b17c909acd4d2bd727ede8f9 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 06:09:00 -0800 Subject: [PATCH 04/21] Added support for -Save with -All parameter --- Public/Helpers/Set-CredentialsLocallyStored.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index a86bd8a..8ba2573 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -41,6 +41,7 @@ function Set-CredentialsLocallyStored { [Parameter(ParameterSetName = 'Automate')] [Parameter(ParameterSetName = 'Control')] + [Parameter(ParameterSetName = 'All')] [switch]$Save, [Parameter(ParameterSetName = 'Automate')] From 33582b5415a1b76e0775ce9b7ada029f8d55b4dc Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 06:10:21 -0800 Subject: [PATCH 05/21] Added -All parameter --- Public/Helpers/Get-CredentialsLocallyStored.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Public/Helpers/Get-CredentialsLocallyStored.ps1 b/Public/Helpers/Get-CredentialsLocallyStored.ps1 index a1cffeb..8788924 100644 --- a/Public/Helpers/Get-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Get-CredentialsLocallyStored.ps1 @@ -7,6 +7,9 @@ function Get-CredentialsLocallyStored { [Parameter(ParameterSetName = 'Control')] [switch]$Control, + [Parameter(ParameterSetName="All")] + [switch]$All, + [Parameter(ParameterSetName = 'Custom',Mandatory=$True)] [string]$CredentialPath, @@ -16,6 +19,11 @@ function Get-CredentialsLocallyStored { ) + if ($All) { + $Automate = $True + $Control = $True + } + if ($Automate) { if (-not (Test-Path "$($CredentialDirectory)Automate - Credentials.txt")) { throw [System.IO.FileNotFoundException] "Automate Credentials not found at $($CredentialDirectory)Automate - Credentials.txt"} $StoreVariables = @( From d8d90550d2f38e5872c4687f2a1c177d8595a43b Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 15:00:40 -0800 Subject: [PATCH 06/21] Updates to use Automate Control Extension for Invoke-ControlCommand --- Public/Compare-AutomateControlStatus.ps1 | 2 +- Public/Invoke-ControlCommand2.ps1 | 35 +++++++----------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/Public/Compare-AutomateControlStatus.ps1 b/Public/Compare-AutomateControlStatus.ps1 index 6659d98..c205d84 100644 --- a/Public/Compare-AutomateControlStatus.ps1 +++ b/Public/Compare-AutomateControlStatus.ps1 @@ -30,7 +30,7 @@ function Compare-AutomateControlStatus { If(!$Quiet){Write-Host -BackgroundColor Blue -ForegroundColor White "Checking to see if the recommended Internal Monitor is present"} $AutoControlSessions=@{}; $InternalMonitorMethod = $false - $Null=Get-AutomateAPIGeneric -Endpoint "InternalMonitorResults" -allresults -condition "(Name like '%GetControlSessionIDs%')" | foreach-object {$AutoControlSessions.Add($_.computerid,$_.IdentityField)}; + $Null=Get-AutomateAPIGeneric -Endpoint "InternalMonitorResults" -allresults -condition "(Name like '%GetControlSessionIDs%')" | Where-Object {($_.computerid -and $_.computerid -gt 0 -and $_.IdentityField -and $_.IdentityField -match '.+')} | ForEach-Object {$AutoControlSessions.Add($_.computerid,$_.IdentityField)}; # Check to see if the Internal Monitor method has results if ($AutoControlSessions.Count -gt 0){$InternalMonitorMethod = $true; If(!$Quiet){Write-Host -BackgroundColor Green -ForegroundColor Black "Internal monitor found. Processing results."} } Else {If(!$Quiet){Write-Host -ForegroundColor Black -BackgroundColor Yellow "Internal monitor not found. This cmdlet is significantly faster with it. See https://www.github.com/gavsto/automateapi"}} diff --git a/Public/Invoke-ControlCommand2.ps1 b/Public/Invoke-ControlCommand2.ps1 index 4508c26..fab97c3 100644 --- a/Public/Invoke-ControlCommand2.ps1 +++ b/Public/Invoke-ControlCommand2.ps1 @@ -1,4 +1,4 @@ -function Invoke-ControlCommand { +function Invoke-ControlCommand2 { <# .SYNOPSIS Will issue a command against a given machine and return the results. @@ -39,7 +39,6 @@ function Invoke-ControlCommand { [string]$Command, [int]$TimeOut = 10000, [switch]$PowerShell, -# [string]$Group = "All Machines", [int]$MaxLength = 5000 ) @@ -117,22 +116,15 @@ function Invoke-ControlCommand { return } - #Get time command was executed -# $epoch = $((New-TimeSpan -Start $(Get-Date -Date "01/01/1970") -End $(Get-Date)).TotalSeconds) -# $ExecuteTime = $epoch - ((($SessionDetails.events | Where-Object {$_.EventType -eq 44})[-1]).Time /1000) -# $ExecuteDate = $origin.AddSeconds($ExecuteTime) - - # Look for results of command - We SHOULD be getting the SessionConnectionEvent data. - $Body=ConvertTo-Json @("SessionConnectionEvent",@(""),@("SessionID","Time","Data"),"SessionID='$GUID' AND EventType='RanCommand' AND Time>='$EventDate'","",100) -Compress -# $Body=ConvertTo-Json @("SessionConnectionEvent",@(""),@("SessionID","Time","Data"),"SessionID=""$GUID""","",100) -Compress -# $Body=ConvertTo-Json @("SessionEvent",@("SessionID"),@("SessionID","Time"),"","",100) -Compress -# $Body=ConvertTo-Json @("SessionEvent",@("SessionID","Time","Data"),@(""),'SessionID="19d61321-9c2e-42e5-addc-fe416d1e88be"',"",10) -Compress + # Look for results of command + $Body=ConvertTo-Json @("SessionConnectionEvent",@(),@("SessionID","Time","Data"),"SessionID='$GUID' AND EventType='RanCommand' AND Time>='$EventDate'","",100) -Compress $RESTRequest = @{ 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" 'Method' = 'POST' 'ContentType' = 'application/json' 'Body' = $Body } + If ($Script:ControlAPIKey) { $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) } Else { @@ -142,25 +134,19 @@ function Invoke-ControlCommand { $Looking = $True $TimeOutDateTime = (Get-Date).AddMilliseconds($TimeOut) while ($Looking) { + Start-Sleep -Seconds 1 try { - $SessionDetails = Invoke-RestMethod @RESTRequest + $SessionEvents = Invoke-RestMethod @RESTRequest } catch { Write-Error $($_.Exception.Message) return } - $ConnectionsWithData = @() - Foreach ($Connection in $SessionDetails.connections) { - $ConnectionsWithData += $Connection | Where-Object {$_.Events.EventType -eq 70} - } - - $Events = ($ConnectionsWithData.events | Where-Object {$_.EventType -eq 70 -and $_.Time}) + $FNames=$SessionEvents.FieldNames + $Events = ($SessionEvents.Items | ForEach-Object {$x=$_; $SCEventRecord = [pscustomobject]@{}; for($i=0; $i -lt $FNames.Length; $i++){$Null = $SCEventRecord | Add-Member -NotePropertyName $FNames[$i] -NotePropertyValue $x[$i]}; $SCEventRecord}) foreach ($Event in $Events) { - $epoch = $((New-TimeSpan -Start $(Get-Date -Date "01/01/1970") -End $(Get-Date)).TotalSeconds) - $CheckTime = $epoch - ($Event.Time /1000) - $CheckDate = $origin.AddSeconds($CheckTime) - if ($CheckDate -gt $ExecuteDate) { + if ($Event.Time -ge $EventDate) { $Looking = $False $Output = $Event.Data -split '[\r\n]' | Where-Object {$_} if(!$PowerShell){ @@ -170,8 +156,7 @@ function Invoke-ControlCommand { } } - Start-Sleep -Seconds 1 - if ($(Get-Date) -gt $TimeOutDateTime.AddSeconds(1)) { + if ($Looking -and $(Get-Date) -gt $TimeOutDateTime.AddSeconds(1)) { $Looking = $False $Output = "Command timed out when sent to Agent" } From 6123ad36632357022062e847f09a119cf158fded Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 15:33:01 -0800 Subject: [PATCH 07/21] Update Connect-ControlAPI tests for API and Credential auth methods --- Public/Connect-ControlAPI.ps1 | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index b288b94..64599a2 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -38,7 +38,7 @@ function Connect-ControlAPI { [String]$Server = $Script:ControlServer, [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] - [String]$APIKey = ($Script:ControlAPIKey), + $APIKey = ($Script:ControlAPIKey), # [Parameter(ParameterSetName = 'credential', Mandatory = $False)] # [String]$TwoFactorToken, @@ -72,6 +72,7 @@ function Connect-ControlAPI { Process { If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and $Null -ne $ControlCredentials) { + Write-Debug "Skipping validation. Setting Server=$($Server) and Credentials=$($ControlCredentials.Username)" $Script:ControlAPICredentials = $ControlCredentials $Script:ControlServer = $Server Return @@ -83,7 +84,7 @@ function Connect-ControlAPI { # Retrieve Control Instance ID to verify APIKey $RESTRequest = @{ 'Method' = 'GET' - 'URI' = "$($Server)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/Service.ashx/GetInstanceID" + 'URI' = "$($Server)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/Service.ashx/GetServerVersion" 'Headers' = @{'CWAIKToken' = (Get-CWAIKToken -APIKey $APIKey)} } Try { @@ -96,26 +97,34 @@ function Connect-ControlAPI { } Return } Else { - If (!$AuthorizationToken) {$AuthorizationToken = $Script:ControlAPICredentials} + If (!$testCredentials) {$testCredentials = $Script:ControlAPICredentials} Do { $ControlAPITestURI = ($Server + '/Services/PageService.ashx/GetHostSessionInfo') If (!$Quiet) { - If (!$ControlCredentials -and ($Force -or !$AuthorizationToken)) { - $AuthorizationToken = $Null + If (!$ControlCredentials -and ($Force -or !$testCredentials)) { + Write-Debug "No Credentials were provided and no existing Token was found, or -Force was specified" + $testCredentials = $Null $Username = Read-Host -Prompt "Please enter your Control Username" $Password = Read-Host -Prompt "Please enter your Control Password" -AsSecureString $ControlCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) } If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') { + Write-Debug "2FA detected as required. (Someday)" $TwoFactorToken = Read-Host -Prompt "Please enter your 2FA Token" } } - #Invoke the REST Method Write-Debug "Submitting Request to $ControlAPITestURI" - If (!$AuthorizationToken) {$AuthorizationToken=$ControlCredentials} + If (!$testCredentials) {$testCredentials=$ControlCredentials} + #Invoke the REST Method + $RESTRequest = @{ + 'URI' = "$ControlAPITestURI" + 'Method' = 'GET' + 'ContentType' = 'application/json' + 'Credential' = $testCredentials + } Try { - $ControlAPITokenResult = Invoke-RestMethod -Uri $ControlAPITestURI -Method Get -Credential $AuthorizationToken + $ControlAPITokenResult = Invoke-RestMethod @RESTRequest } Catch { $Script:ControlAPICredentials = $Null @@ -124,7 +133,8 @@ function Connect-ControlAPI { Return } } - $AuthorizationResult=$ControlAPITokenResult.Version + Write-Debug "Request Results: $($ControlAPITokenResult|ConvertTo-Json -Depth 5 -Compress)" + $AuthorizationResult=$ControlAPITokenResult.ProductVersion $TwoFactorNeeded=$ControlAPITokenResult.IsTwoFactorRequired } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationResult) -or ($TwoFactorNeeded -ne $True -and $ControlCredentials) -or @@ -134,7 +144,7 @@ function Connect-ControlAPI { } End { - If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey) -or ($PSCmdlet.ParameterSetName -ne 'apikey' -and [string]::IsNullOrEmpty($AuthorizationToken))) { + If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey) -or ($PSCmdlet.ParameterSetName -ne 'apikey' -and [string]::IsNullOrEmpty($testCredentials))) { Throw "Unable to get Access Token. Either the credentials your entered are incorrect or you did not pass a valid two factor token" If ($Quiet) { Return $False @@ -143,7 +153,7 @@ function Connect-ControlAPI { If ($PSCmdlet.ParameterSetName -eq 'apikey') { $Script:ControlAPIKey = $APIKey } Else { - $Script:ControlAPICredentials = $AuthorizationToken + $Script:ControlAPICredentials = $testCredentials } $Script:ControlServer = $Server If (!$Quiet) { From e29ea466b693adf00a9698eb5a923a82a066bd5a Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 19:11:15 -0800 Subject: [PATCH 08/21] Updating Credentials Storage and Retrieval --- Public/Compare-AutomateControlStatus.ps1 | 4 +- Public/Connect-AutomateAPI.ps1 | 96 ++++++++++---- .../Helpers/Get-CredentialsLocallyStored.ps1 | 86 ++++++------ .../Helpers/Set-CredentialsLocallyStored.ps1 | 125 ++++++++++-------- 4 files changed, 186 insertions(+), 125 deletions(-) diff --git a/Public/Compare-AutomateControlStatus.ps1 b/Public/Compare-AutomateControlStatus.ps1 index c205d84..bf67247 100644 --- a/Public/Compare-AutomateControlStatus.ps1 +++ b/Public/Compare-AutomateControlStatus.ps1 @@ -30,7 +30,7 @@ function Compare-AutomateControlStatus { If(!$Quiet){Write-Host -BackgroundColor Blue -ForegroundColor White "Checking to see if the recommended Internal Monitor is present"} $AutoControlSessions=@{}; $InternalMonitorMethod = $false - $Null=Get-AutomateAPIGeneric -Endpoint "InternalMonitorResults" -allresults -condition "(Name like '%GetControlSessionIDs%')" | Where-Object {($_.computerid -and $_.computerid -gt 0 -and $_.IdentityField -and $_.IdentityField -match '.+')} | ForEach-Object {$AutoControlSessions.Add($_.computerid,$_.IdentityField)}; + $Null=Get-AutomateAPIGeneric -Endpoint "InternalMonitorResults" -allresults -condition "(Name like '%GetControlSessionIDs%')" -EA 0 | Where-Object {($_.computerid -and $_.computerid -gt 0 -and $_.IdentityField -and $_.IdentityField -match '.+')} | ForEach-Object {$AutoControlSessions.Add($_.computerid,$_.IdentityField)}; # Check to see if the Internal Monitor method has results if ($AutoControlSessions.Count -gt 0){$InternalMonitorMethod = $true; If(!$Quiet){Write-Host -BackgroundColor Green -ForegroundColor Black "Internal monitor found. Processing results."} } Else {If(!$Quiet){Write-Host -ForegroundColor Black -BackgroundColor Yellow "Internal monitor not found. This cmdlet is significantly faster with it. See https://www.github.com/gavsto/automateapi"}} @@ -39,7 +39,7 @@ function Compare-AutomateControlStatus { if(!$ObjectRebuild.Count -gt 0){$FullLookupMethod = $true} if ($FullLookupMethod) { - $ObjectRebuild = Get-AutomateComputer -AllComputers | Select Id, ComputerName, @{Name = 'ClientName'; Expression = {$_.Client.Name}}, OperatingSystemName, Status + $ObjectRebuild = Get-AutomateComputer -AllComputers | Select-Object Id, ComputerName, @{Name = 'ClientName'; Expression = {$_.Client.Name}}, OperatingSystemName, Status } foreach ($computer in $ObjectRebuild) { diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index 043f25b..fd12678 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -12,6 +12,8 @@ Takes a standard powershell credential object, this can be built with $Credentia Takes a string that represents the 2FA number .PARAMETER Token Used internally when quietly refreshing the Token +.PARAMETER Verify +Specifies to test the current token, and if it is not valid attempt to obtain a new one using the current credentials. Does not refresh (re-issue) the current token. .PARAMETER Force Will not attempt to refresh a current session .PARAMETER Quiet @@ -41,14 +43,19 @@ Connect-AutomateAPI -Quiet [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] [String]$Server = $Script:CWAServer, [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] [String]$AuthorizationToken = ($Script:CWAToken.Authorization -replace 'Bearer ',''), [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] [Switch]$SkipCheck, + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] + [Switch]$Verify, + [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [String]$TwoFactorToken, @@ -57,6 +64,7 @@ Connect-AutomateAPI -Quiet [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] [Switch]$Quiet ) @@ -87,13 +95,22 @@ Connect-AutomateAPI -Quiet $Script:CWAToken = $AutomateToken Return } + If (!$AuthorizationToken -and $PSCmdlet.ParameterSetName -eq 'verify') { + Throw "Attempt to verify token failed. No token was provided or was cached." + Return + } Do { - $AutomateAPIURI = ('https://' + $Server + '/cwa/api/v1/apitoken') + Write-Debug "Starting Authorizaton Test." + $AutomateAPIURI = ('https://' + $Server + '/cwa/api/v1') If (!$Quiet) { If (!$AutomateCredentials -and ($Force -or !$AuthorizationToken)) { - $Username = Read-Host -Prompt "Please enter your Automate Username" - $Password = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString - $AutomateCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + If ($PSCmdlet.ParameterSetName -eq 'verify' -and $Script:CWACredentials) { + $AutomateCredentials = $Script:CWACredentials + } Else { + $Username = Read-Host -Prompt "Please enter your Automate Username" + $Password = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString + $AutomateCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + } } If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') { $TwoFactorToken = Read-Host -Prompt "Please enter your 2FA Token" @@ -102,38 +119,67 @@ Connect-AutomateAPI -Quiet If ($AutomateCredentials) { #Build the headers for the Authentication - $PostHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $PostHeaders.Add("username", $AutomateCredentials.UserName) - $PostHeaders.Add("password", $AutomateCredentials.GetNetworkCredential().Password) + $PostBody = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $PostBody.Add("username", $AutomateCredentials.UserName) + $PostBody.Add("password", $AutomateCredentials.GetNetworkCredential().Password) If (!([string]::IsNullOrEmpty($TwoFactorToken))) { #Remove any spaces that were added $TwoFactorToken = $TwoFactorToken -replace '\s', '' - $PostHeaders.Add("TwoFactorPasscode", $TwoFactorToken) + $PostBody.Add("TwoFactorPasscode", $TwoFactorToken) + } + $RESTRequest = @{ + 'URI' = ($AutomateAPIURI + '/apitoken') + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $($PostBody | ConvertTo-Json -Compress) + } + } ElseIf ($PSCmdlet.ParameterSetName -eq 'refresh') { + $PostBody = $AuthorizationToken -replace 'Bearer ','' + $RESTRequest = @{ + 'URI' = ($AutomateAPIURI + '/apitoken/refresh') + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $PostBody | ConvertTo-Json -Compress + } + } ElseIf ($PSCmdlet.ParameterSetName -eq 'verify') { + $PostBody = $AuthorizationToken -replace 'Bearer ','' + $RESTRequest = @{ + 'URI' = ($AutomateAPIURI + '/DatabaseServerTime') + 'Method' = 'GET' + 'ContentType' = 'application/json' + 'Headers' = @{'Authorization' = "Bearer $PostBody"} } - } Else { - $AutomateAPIURI = $AutomateAPIURI + '/refresh' - $PostHeaders = $AuthorizationToken -replace 'Bearer ','' } - #Convert the body to JSON for Posting - $PostBody = $PostHeaders | ConvertTo-Json -Compress #Invoke the REST Method - Write-Debug "Submitting Request to $AutomateAPIURI with body: `n$PostBody" + Write-Debug "Submitting Request to $($RESTRequest.URI)`nHeaders:`n$($RESTRequest.Headers|ConvertTo-JSON -Depth 5)`nBody:`n$($RESTRequest.Body|ConvertTo-JSON -Depth 5)" Try { - $AutomateAPITokenResult = Invoke-RestMethod -Method Post -Uri $AutomateAPIURI -Body $PostBody -ContentType "application/json" + $AutomateAPITokenResult = Invoke-RestMethod @RESTRequest } Catch { + Write-Debug "Request failed. Clearing Cached Token." $Script:CWAToken = $Null $Script:CWATokenKey = $Null - $Script:CWACredentials = $Null - $Script:CWACredentialsExpirationDate = $Null + If ($AutomateCredentials -or $PSCmdlet.ParameterSetName -ne 'verify') { + Write-Debug "Request failed with credentials. Clearing Cached Credentials." + $Script:CWACredentials = $Null + } If ($AutomateCredentials) { Throw "Attempt to authenticate to the Automate API has failed with error $_.Exception.Message" Return } } +# Write-Debug "Member Info: $(($AutomateAPITokenResult | Get-Member) -split '`n')" +# Write-Debug "JSON View: $(($AutomateAPITokenResult | ConvertTo-Json -Depth 100) -split '`n')" +# Write-Debug "Request Result: $($AutomateAPITokenResult|ConvertTo-JSON -Depth 5 -Compress)" + $AuthorizationToken=$AutomateAPITokenResult.Accesstoken $TwoFactorNeeded=$AutomateAPITokenResult.IsTwoFactorRequired + If ($PSCmdlet.ParameterSetName -eq 'verify' -and [string]::IsNullOrEmpty($AuthorizationToken)) { + Write-Debug "Re-Initializing AuthorizationToken with ""$($Script:CWAToken.Authorization)""" + $AuthorizationToken = $Script:CWAToken.Authorization -replace 'Bearer ','' + } + Write-Debug "Authorization Token Value: ""$($AuthorizationToken)""" } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationToken) -or ($TwoFactorNeeded -ne $True -and $AutomateCredentials) -or ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') @@ -147,23 +193,23 @@ Connect-AutomateAPI -Quiet Return $False } } Else { - - Write-Verbose "Token retrieved: $AuthorizationToken, expiration is $($AutomateAPITokenResult.ExpirationDate)" - #Build the returned token $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") Write-Debug "Setting Credentials to $($AutomateToken.Authorization)" #Create Script Variables for this session in order to use the token $Script:CWAServer = ("https://" + $Server) - $Script:CWACredentials = $AutomateCredentials - $Script:CWATokenKey = ConvertTo-SecureString $AutomateAPITokenResult.AccessToken -AsPlainText -Force + If ($AutomateCredentials -or $PSCmdlet.ParameterSetName -ne 'verify') { + $Script:CWACredentials = $AutomateCredentials + $Script:CWATokenKey = ConvertTo-SecureString $AuthorizationToken -AsPlainText -Force + $AutomateAPITokenResult.PSObject.properties.remove('AccessToken') + $Script:CWATokenInfo = $AutomateAPITokenResult + } $Script:CWAToken = $AutomateToken - $AutomateAPITokenResult.PSObject.properties.remove('AccessToken') - $Script:CWATokenInfo = $AutomateAPITokenResult + Write-Verbose "Token retrieved: $AuthorizationToken, expiration is $($Script:CWATokenInfo.ExpirationDate)" If (!$Quiet) { - Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Automate REST API. Token will expire at $($AutomateAPITokenResult.ExpirationDate)" + Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Automate REST API. Token will expire at $($Script:CWATokenInfo.ExpirationDate)" } Else { Return $True } diff --git a/Public/Helpers/Get-CredentialsLocallyStored.ps1 b/Public/Helpers/Get-CredentialsLocallyStored.ps1 index 8788924..3ec300c 100644 --- a/Public/Helpers/Get-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Get-CredentialsLocallyStored.ps1 @@ -19,26 +19,37 @@ function Get-CredentialsLocallyStored { ) - if ($All) { + If ($All) { $Automate = $True $Control = $True } - if ($Automate) { - if (-not (Test-Path "$($CredentialDirectory)Automate - Credentials.txt")) { throw [System.IO.FileNotFoundException] "Automate Credentials not found at $($CredentialDirectory)Automate - Credentials.txt"} + If ($Automate) { + $CredentialPath = "$($CredentialDirectory)\Automate - Credentials.txt" + If (-not (Test-Path $CredentialPath -EA 0)) { + Throw [System.IO.FileNotFoundException] "Automate Credentials not found at $($CredentialPath)" + } $StoreVariables = @( @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenInfo'; 'Scope' = 'Script'} ) - $StoreBlock = Get-Content "$($CredentialDirectory)Automate - Credentials.txt" | ConvertFrom-Json + $StoreBlock = Get-Content $CredentialPath | ConvertFrom-Json Foreach ($SaveVar in $StoreVariables) { If (!($StoreBlock.$($SaveVar.Name))) {Continue} If ($SaveVar.Name -match 'Credential') { - $Null = Set-Variable @SaveVar -Value $(New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.$($SaveVar.Name).Username), $($StoreBlock.$($SaveVar.Name).Password|ConvertTo-SecureString)) + Try { + $Null = Set-Variable @SaveVar -Value $(New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.$($SaveVar.Name).Username), $(ConvertTo-SecureString $($StoreBlock.$($SaveVar.Name).Password))) + } Catch { + Write-Warning "Failed to restore $($SaveVar.Name). The stored password is invalid." + } } ElseIf ($SaveVar.Name -match 'Key') { - $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)|ConvertTo-SecureString) + Try { + $Null = Set-Variable @SaveVar -Value $(ConvertTo-SecureString $($StoreBlock.$($SaveVar.Name))) + } Catch { + Write-Warning "Failed to restore $($SaveVar.Name). The stored secure value is invalid." + } } Else { $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)) } @@ -49,61 +60,52 @@ function Get-CredentialsLocallyStored { $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") $Script:CWAToken = $AutomateToken } - -# $LocalAutomateUsername = $LocalAutomateCredentials[0] -# $LocalAutomatePassword = $LocalAutomateCredentials[1] | ConvertTo-SecureString -# $LocalAutomateServer = $LocalAutomateCredentials[2] -# $LocalAutomateCredentialsObject = New-Object System.Management.Automation.PSCredential -ArgumentList $LocalAutomateUsername, $LocalAutomatePassword -# try { -# Connect-AutomateAPI -AutomateCredentials $LocalAutomateCredentialsObject -Server $LocalAutomateServer -# } -# catch { -# Write-Error $_ -# } } - if ($Control) { - if (-not (Test-Path "$($CredentialDirectory)Control - Credentials.txt")) { throw [System.IO.FileNotFoundException] "Control Credentials not found at $($CredentialDirectory)Control - Credentials.txt"} + If ($Control) { + $CredentialPath = "$($CredentialDirectory)\Control - Credentials.txt" + If (-not (Test-Path $CredentialPath -EA 0)) { + Throw [System.IO.FileNotFoundException] "Control Credentials not found at $($CredentialPath)" + } $StoreVariables = @( @{'Name' = 'ControlAPICredentials'; 'Scope' = 'Script'}, @{'Name' = 'ControlServer'; 'Scope' = 'Script'}, @{'Name' = 'ControlAPIKey'; 'Scope' = 'Script'} ) - $StoreBlock = Get-Content "$($CredentialDirectory)Control - Credentials.txt" | ConvertFrom-Json + $StoreBlock = Get-Content $CredentialPath | ConvertFrom-Json Foreach ($SaveVar in $StoreVariables) { If (!($StoreBlock.$($SaveVar.Name))) {Continue} If ($SaveVar.Name -match 'Credential') { - $Null = Set-Variable @SaveVar -Value $(New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.$($SaveVar.Name).Username), $($StoreBlock.$($SaveVar.Name).Password|ConvertTo-SecureString)) + Try { + $Null = Set-Variable @SaveVar -Value $(New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.$($SaveVar.Name).Username), $(ConvertTo-SecureString $($StoreBlock.$($SaveVar.Name).Password))) + } Catch { + Write-Warning "Failed to restore $($SaveVar.Name). The stored password is invalid." + } } ElseIf ($SaveVar.Name -match 'Key') { - $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)|ConvertTo-SecureString) + Try { + $Null = Set-Variable @SaveVar -Value $(ConvertTo-SecureString $($StoreBlock.$($SaveVar.Name))) + } Catch { + Write-Warning "Failed to restore $($SaveVar.Name). The stored secure value is invalid." + } } Else { $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)) } } - -# $LocalControlUsername = $LocalControlCredentials[0] -# $LocalControlPassword = $LocalControlCredentials[1] | ConvertTo-SecureString -# $LocalControlServer = $LocalControlCredentials[2] -# $LocalControlCredentialsObject = New-Object System.Management.Automation.PSCredential -ArgumentList $LocalControlUsername, $LocalControlPassword -# try { -# Connect-ControlAPI -ControlCredentials $LocalControlCredentialsObject -Server $LocalControlServer -ErrorAction Stop -# } -# catch { -# Write-Error "Unable to store or retrieve Control credentials with error $_.Exception.Message" -# } } - if ($Custom) { - if (-not (Test-Path "$($CredentialPath)")) { throw [System.IO.FileNotFoundException] "Credentials not found at $($CredentialPath)"} + If ($Custom) { + If (-not (Test-Path "$($CredentialPath)")) { + Throw [System.IO.FileNotFoundException] "Credentials not found at $($CredentialPath)" + } $StoreBlock = Get-Content $CredentialPath | ConvertFrom-Json - If (!($StoreBlock.CustomCredentials)) {Continue} - -# $CustomCredentials = Get-Content $CredentialPath -# $CustomUsername = $CustomCredentials[0] -# $CustomPassword = $CustomCredentials[1] | ConvertTo-SecureString - $CustomCredentialObject = New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.CustomCredentials.Username), $($StoreBlock.CustomCredentials.Password|ConvertTo-SecureString) - return $CustomCredentialObject + + Try { + $CustomCredentialObject = New-Object System.Management.Automation.PSCredential -ArgumentList $($StoreBlock.CustomCredentials.Username), $(ConvertTo-SecureString $($StoreBlock.CustomCredentials.Password)) + } Catch { + Write-Warning "Failed to restore CustomCredential from $($CredentialPath). The stored password is invalid." + } + Return $CustomCredentialObject } } \ No newline at end of file diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index 8ba2573..6ac4d0d 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -9,17 +9,8 @@ function Set-CredentialsLocallyStored { .EXAMPLE Set-CredentialsLocallyStored -Automate -.EXAMPLE - Set-CredentialsLocallyStored -ITGlue - -.EXAMPLE - Set-CredentialsLocallyStored -MySQL - -.EXAMPLE - Set-CredentialsLocallyStored -Office365 - .Example - Set-CredentialsLocallyStored -Custom -CredentialPath "C:\Credentials\Custom Credentials.txt" + Set-CredentialsLocallyStored -Custom -CredentialDisplayName 'Office365' -CredentialDirectory "C:\Credentials" #> [CmdletBinding()] @@ -50,103 +41,125 @@ function Set-CredentialsLocallyStored { [string]$CredentialDirectory = "$($env:USERPROFILE)\AutomateAPI\" ) - if ($All) { + If ($All) { $Automate = $True $Control = $True } - if (-not (Test-Path $CredentialDirectory)) { + If (-not (Test-Path $CredentialDirectory)) { New-Item -ItemType Directory -Force -Path $CredentialDirectory | ForEach-Object{$_.Attributes = "hidden"} } - if ($Automate) { + If ($Automate) { $StoreVariables = @( @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenKey'; 'Scope' = 'Script'}, @{'Name' = 'CWATokenInfo'; 'Scope' = 'Script'} ) + + If (!$Save) { + Connect-AutomateAPI -Server '' -Force + } + $StoreBlock = [pscustomobject]@{} - $CredentialPath = "$($CredentialDirectory)Automate - Credentials.txt" - - If ($Save) { - Foreach ($SaveVar in $StoreVariables) { - If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} - If ($SaveVar.Name -match 'Credential') { - $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} - } ElseIf ($SaveVar.Name -match 'Key') { - $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly|ConvertFrom-SecureString) - } Else { - $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) + $CredentialPath = "$($CredentialDirectory)\Automate - Credentials.txt" + + Foreach ($SaveVar in $StoreVariables) { + If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} + If ($SaveVar.Name -match 'Credential') { + Try { + $x_Credential = @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue $x_Credential + } Catch { + Write-Warning "Failed to store $($SaveVar.Name), it is not a valid Credential." } + } ElseIf ($SaveVar.Name -match 'Key') { + Try { + $x_Key = (Get-Variable @SaveVar -ValueOnly|ConvertFrom-SecureString) + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue $x_Key + } Catch { + Write-Warning "Failed to store $($SaveVar.Name), it is not a valid Secure String." + } + } Else { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) } + } +<# } Else { - $TempAutomateServer = Read-Host -Prompt "Please enter your Automate Server address, without the HTTPS, IE: rancor.hostedrmm.com" + $TempAutomateServer = $Null + Do { + $TempAutomateServer = Read-Host -Prompt "Please enter your Automate Server address, IE: rancor.hostedrmm.com" + $TempAutomateServer = $TempAutomateServer -replace '^https?://','' -replace '/.*','' + If (!($TempAutomateServer -match '^[a-z0-9][a-z0-9\.\-]*$')) {Write-Host "Server address is missing or in invalid format."} + } Until ($TempAutomateServer -match '^[a-z0-9][a-z0-9\.\-]*$') $TempAutomateUsername = Read-Host -Prompt "Please enter your Automate Username" $TempAutomatePassword = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString -# $TempAutomatePassword = $TempAutomatePassword | ConvertFrom-SecureString $Null = $StoreBlock | Add-Member -NotePropertyName 'CWAServer' -NotePropertyValue $TempAutomateServer $Null = $StoreBlock | Add-Member -NotePropertyName 'CWACredentials' -NotePropertyValue @{'UserName'=$TempAutomateUsername; 'Password'=($TempAutomatePassword | ConvertFrom-SecureString)} } +#> $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline - -# Set-Content "$CredentialPath" $TempAutomateUsername -Force -# Add-Content "$CredentialPath" $TempAutomatePassword -# Add-Content "$CredentialPath" $TempAutomateServer Write-Output "Automate Credentials Set" } - if ($Control) { + If ($Control) { $StoreVariables = @( @{'Name' = 'ControlAPICredentials'; 'Scope' = 'Script'}, @{'Name' = 'ControlServer'; 'Scope' = 'Script'}, @{'Name' = 'ControlAPIKey'; 'Scope' = 'Script'} ) + + If (!$Save) { + Connect-ControlAPI -Server '' -Force + } + $StoreBlock = [pscustomobject]@{} - $CredentialPath = "$($CredentialDirectory)Control - Credentials.txt" - - If ($Save) { - Foreach ($SaveVar in $StoreVariables) { - If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} - If ($SaveVar.Name -match 'Credential') { - $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} - } ElseIf ($SaveVar.Name -match 'Key') { - $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly|ConvertFrom-SecureString) - } Else { - $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) + $CredentialPath = "$($CredentialDirectory)\Control - Credentials.txt" + + Foreach ($SaveVar in $StoreVariables) { + If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} + If ($SaveVar.Name -match 'Credential') { + Try { + $x_Credential = @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue $x_Credential + } Catch { + Write-Warning "Failed to store $($SaveVar.Name), it is not a valid Credential." } + } ElseIf ($SaveVar.Name -match 'Key') { + Try { + $x_Key = (Get-Variable @SaveVar -ValueOnly|ConvertFrom-SecureString) + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue $x_Key + } Catch { + Write-Warning "Failed to store $($SaveVar.Name), it is not a valid Secure String." + } + } Else { + $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) } - } Else { + } +<# } Else { $TempControlServer = Read-Host -Prompt "Please enter your Control Server address, the full URL. IE https://control.rancorthebeast.com:8040" $TempControlUsername = Read-Host -Prompt "Please enter your Control Username" $TempControlPassword = Read-Host -Prompt "Please enter your Control Password" -AsSecureString -# $TempControlPassword = $TempControlPassword | ConvertFrom-SecureString $Null = $StoreBlock | Add-Member -NotePropertyName 'ControlServer' -NotePropertyValue $TempControlServer $Null = $StoreBlock | Add-Member -NotePropertyName 'ControlAPICredentials' -NotePropertyValue @{'UserName'=$TempControlUsername; 'Password'=($TempControlPassword | ConvertFrom-SecureString)} - } - +# } +#> + $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline -# Set-Content "$CredentialPath" $TempControlUsername -Force -# Add-Content "$CredentialPath" $TempControlPassword -# Add-Content "$CredentialPath" $TempControlServer Write-Output "Control Credentials Set" } - if ($Custom) { + If ($Custom) { $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)\$($CredentialDisplayName).txt" $CustomCredentials = Get-Credential -Message "Please enter the Custom Username and Password to store" -# $CustomUsername = $CustomCredentials.UserName -# $CustomPasswordSecureString = $CustomCredentials.Password -# $CustomPassword = $CustomPasswordSecureString | ConvertFrom-SecureString $Null = $StoreBlock | Add-Member -NotePropertyName 'CustomCredentials' -NotePropertyValue @{'UserName'=$CustomCredentials.Password; 'Password'=($CustomCredentials.Password | ConvertFrom-SecureString)} $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline - # Set-Content "$CredentialPath" $CustomUsername -Force - # Add-Content "$CredentialPath" $CustomPassword - Write-Output "Custom Credentials Set" + Write-Output "Custom Credentials Set for $($CredentialDisplayName)" } } \ No newline at end of file From 2cb673fed840fad276843549393860651a9806ab Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 19:19:42 -0800 Subject: [PATCH 09/21] Credential Storage Fixes --- .../Helpers/Set-CredentialsLocallyStored.ps1 | 41 ++++--------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index 6ac4d0d..72a2daa 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -50,8 +50,11 @@ function Set-CredentialsLocallyStored { New-Item -ItemType Directory -Force -Path $CredentialDirectory | ForEach-Object{$_.Attributes = "hidden"} } - If ($Automate) { + If (!$Save) { + Connect-AutomateAPI -Server '' -Force + } + $StoreVariables = @( @{'Name' = 'CWAServer'; 'Scope' = 'Script'}, @{'Name' = 'CWACredentials'; 'Scope' = 'Script'}, @@ -59,10 +62,6 @@ function Set-CredentialsLocallyStored { @{'Name' = 'CWATokenInfo'; 'Scope' = 'Script'} ) - If (!$Save) { - Connect-AutomateAPI -Server '' -Force - } - $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)\Automate - Credentials.txt" @@ -86,36 +85,22 @@ function Set-CredentialsLocallyStored { $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) } } -<# - } Else { - $TempAutomateServer = $Null - Do { - $TempAutomateServer = Read-Host -Prompt "Please enter your Automate Server address, IE: rancor.hostedrmm.com" - $TempAutomateServer = $TempAutomateServer -replace '^https?://','' -replace '/.*','' - If (!($TempAutomateServer -match '^[a-z0-9][a-z0-9\.\-]*$')) {Write-Host "Server address is missing or in invalid format."} - } Until ($TempAutomateServer -match '^[a-z0-9][a-z0-9\.\-]*$') - $TempAutomateUsername = Read-Host -Prompt "Please enter your Automate Username" - $TempAutomatePassword = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString - $Null = $StoreBlock | Add-Member -NotePropertyName 'CWAServer' -NotePropertyValue $TempAutomateServer - $Null = $StoreBlock | Add-Member -NotePropertyName 'CWACredentials' -NotePropertyValue @{'UserName'=$TempAutomateUsername; 'Password'=($TempAutomatePassword | ConvertFrom-SecureString)} - } -#> $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline Write-Output "Automate Credentials Set" } If ($Control) { + If (!$Save) { + Connect-ControlAPI -Server '' -Force + } + $StoreVariables = @( @{'Name' = 'ControlAPICredentials'; 'Scope' = 'Script'}, @{'Name' = 'ControlServer'; 'Scope' = 'Script'}, @{'Name' = 'ControlAPIKey'; 'Scope' = 'Script'} ) - If (!$Save) { - Connect-ControlAPI -Server '' -Force - } - $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)\Control - Credentials.txt" @@ -139,14 +124,6 @@ function Set-CredentialsLocallyStored { $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue (Get-Variable @SaveVar -ValueOnly) } } -<# } Else { - $TempControlServer = Read-Host -Prompt "Please enter your Control Server address, the full URL. IE https://control.rancorthebeast.com:8040" - $TempControlUsername = Read-Host -Prompt "Please enter your Control Username" - $TempControlPassword = Read-Host -Prompt "Please enter your Control Password" -AsSecureString - $Null = $StoreBlock | Add-Member -NotePropertyName 'ControlServer' -NotePropertyValue $TempControlServer - $Null = $StoreBlock | Add-Member -NotePropertyName 'ControlAPICredentials' -NotePropertyValue @{'UserName'=$TempControlUsername; 'Password'=($TempControlPassword | ConvertFrom-SecureString)} -# } -#> $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline Write-Output "Control Credentials Set" @@ -156,7 +133,7 @@ function Set-CredentialsLocallyStored { $StoreBlock = [pscustomobject]@{} $CredentialPath = "$($CredentialDirectory)\$($CredentialDisplayName).txt" $CustomCredentials = Get-Credential -Message "Please enter the Custom Username and Password to store" - $Null = $StoreBlock | Add-Member -NotePropertyName 'CustomCredentials' -NotePropertyValue @{'UserName'=$CustomCredentials.Password; 'Password'=($CustomCredentials.Password | ConvertFrom-SecureString)} + $Null = $StoreBlock | Add-Member -NotePropertyName 'CustomCredentials' -NotePropertyValue @{'UserName'=$CustomCredentials.UserName; 'Password'=($CustomCredentials.Password | ConvertFrom-SecureString)} $StoreBlock | ConvertTo-JSON -Depth 10 | Out-File -FilePath $CredentialPath -Force -NoNewline Write-Output "Custom Credentials Set for $($CredentialDisplayName)" From 1f6d43400cca46d40bc1b173a2e75aa5a664aad8 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 20:44:43 -0800 Subject: [PATCH 10/21] Connect-ControlAPI Fixes --- Private/Get-CWAIKToken.ps1 | 4 +- Public/Connect-ControlAPI.ps1 | 73 ++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/Private/Get-CWAIKToken.ps1 b/Private/Get-CWAIKToken.ps1 index e8812e5..74a363d 100644 --- a/Private/Get-CWAIKToken.ps1 +++ b/Private/Get-CWAIKToken.ps1 @@ -18,6 +18,8 @@ function Get-CWAIKToken { $epochsteps = [long]$((New-TimeSpan -Start $origin -End $(get-date).ToUniversalTime()).TotalSeconds/$TimeStepSeconds) $barray=[System.BitConverter]::GetBytes($epochsteps); [array]::Reverse($barray) $hmacsha = [System.Security.Cryptography.HMACSHA256]::new([Convert]::FromBase64String($APIKey)) - $Local:CWAIKToken = [Convert]::ToBase64String($hmacsha.ComputeHash($barray)) + If ($hmacsha) { + $Local:CWAIKToken = [Convert]::ToBase64String($hmacsha.ComputeHash($barray)) + } Return $Local:CWAIKToken } \ No newline at end of file diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index 64599a2..c6fb914 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -10,10 +10,9 @@ function Connect-ControlAPI { Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass .PARAMETER APIKey Automate APIKey for Control Extension + .PARAMETER Quiet Will not output any standard logging messages - .PARAMETER TestCredentials - Performs a test to the API .OUTPUTS Two script variables with server and credentials. Returns True or False .NOTES @@ -71,9 +70,9 @@ function Connect-ControlAPI { Process { If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} - If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and $Null -ne $ControlCredentials) { - Write-Debug "Skipping validation. Setting Server=$($Server) and Credentials=$($ControlCredentials.Username)" - $Script:ControlAPICredentials = $ControlCredentials + If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and !($Null -eq $Credentials)) { + Write-Debug "Skipping validation. Setting Server=$($Server) and Credentials=$($Credentials.Username)" + $Script:ControlAPICredentials = $Credentials $Script:ControlServer = $Server Return } ElseIf ($PSCmdlet.ParameterSetName -eq 'apikey' -and $Null -ne $APIKey) { @@ -87,9 +86,11 @@ function Connect-ControlAPI { 'URI' = "$($Server)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/Service.ashx/GetServerVersion" 'Headers' = @{'CWAIKToken' = (Get-CWAIKToken -APIKey $APIKey)} } + Write-Debug "Submitting Request to $($RESTRequest.URI)" Try { - $Null = Invoke-RestMethod @RESTRequest + $AuthorizationResult = Invoke-RestMethod @RESTRequest } Catch { + $Script:ControlAPIKey = $Null $APIKey=$Null Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" Return @@ -97,16 +98,26 @@ function Connect-ControlAPI { } Return } Else { - If (!$testCredentials) {$testCredentials = $Script:ControlAPICredentials} + If (!$testCredentials -and !$Force) { + If (!$Credentials) { + $testCredentials = $Script:ControlAPICredentials + } Else { + $testCredentials = $Credentials + } + } Do { $ControlAPITestURI = ($Server + '/Services/PageService.ashx/GetHostSessionInfo') If (!$Quiet) { - If (!$ControlCredentials -and ($Force -or !$testCredentials)) { - Write-Debug "No Credentials were provided and no existing Token was found, or -Force was specified" - $testCredentials = $Null - $Username = Read-Host -Prompt "Please enter your Control Username" - $Password = Read-Host -Prompt "Please enter your Control Password" -AsSecureString - $ControlCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + If (!$Credentials) { + If (!$testCredentials -or $Force) { + Write-Debug "No Credentials were provided and no existing Token was found, or -Force was specified" + $testCredentials = $Null + $Username = Read-Host -Prompt "Please enter your Control Username" + $Password = Read-Host -Prompt "Please enter your Control Password" -AsSecureString + $Credentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + } Else { + $Force = $True + } } If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') { Write-Debug "2FA detected as required. (Someday)" @@ -114,22 +125,22 @@ function Connect-ControlAPI { } } - Write-Debug "Submitting Request to $ControlAPITestURI" - If (!$testCredentials) {$testCredentials=$ControlCredentials} + If (!$testCredentials) {$testCredentials=$Credentials} #Invoke the REST Method $RESTRequest = @{ - 'URI' = "$ControlAPITestURI" + 'URI' = $ControlAPITestURI 'Method' = 'GET' 'ContentType' = 'application/json' 'Credential' = $testCredentials } + Write-Debug "Submitting Request to $($RESTRequest.URI)" Try { $ControlAPITokenResult = Invoke-RestMethod @RESTRequest } Catch { $Script:ControlAPICredentials = $Null - If ($ControlCredentials) { - Throw "Unable to connect to Control. Server or Control Credentials are wrong. This module does not support 2FA for Control Users" + If ($Credentials) { + Throw "Unable to connect to Control. Server Address or Control Credentials are wrong. This module does not support 2FA for Control Users" Return } } @@ -137,17 +148,35 @@ function Connect-ControlAPI { $AuthorizationResult=$ControlAPITokenResult.ProductVersion $TwoFactorNeeded=$ControlAPITokenResult.IsTwoFactorRequired } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationResult) -or - ($TwoFactorNeeded -ne $True -and $ControlCredentials) -or + ($TwoFactorNeeded -ne $True -and $Credentials) -or ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') ) } } - + End { - If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey) -or ($PSCmdlet.ParameterSetName -ne 'apikey' -and [string]::IsNullOrEmpty($testCredentials))) { + If ($SkipCheck) { + If ($Quiet) {Return $True} + Else { + Write-Host -BackgroundColor Green -ForegroundColor Black "Skipping validation. Setting Server=$($Server) and Credentials=$($Credentials.Username)" + } + Return + } + If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey)) { + $Script:ControlAPIKey = $Null + Throw "Unable to validate the APIKey provided." + If ($Quiet) { + Return $False + } Else { + Return + } + } ElseIf ($PSCmdlet.ParameterSetName -ne 'apikey' -and [string]::IsNullOrEmpty($AuthorizationResult)) { + $Script:ControlAPICredentials = $Null Throw "Unable to get Access Token. Either the credentials your entered are incorrect or you did not pass a valid two factor token" If ($Quiet) { Return $False + } Else { + Return } } Else { If ($PSCmdlet.ParameterSetName -eq 'apikey') { @@ -157,7 +186,7 @@ function Connect-ControlAPI { } $Script:ControlServer = $Server If (!$Quiet) { - Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Control API. Server version is $($ControlAPITokenResult.ProductVersion)" + Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully tested and connected to the Control API. Server version is $($AuthorizationResult)" } Else { Return $True } From af9cf3d9a6a21ca4ca867b94ca09d20cade2a864 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 22:14:35 -0800 Subject: [PATCH 11/21] Fixing Connect-AutomateAPI for 2FA, Refresh and Verify options --- Docs/Connect-AutomateAPI.md | 6 +- Public/Connect-AutomateAPI.ps1 | 83 ++++++++++--------- .../Helpers/Set-CredentialsLocallyStored.ps1 | 1 + 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/Docs/Connect-AutomateAPI.md b/Docs/Connect-AutomateAPI.md index 4294bd5..7b2eb60 100644 --- a/Docs/Connect-AutomateAPI.md +++ b/Docs/Connect-AutomateAPI.md @@ -20,7 +20,7 @@ Connect-AutomateAPI [-Server ] [-AuthorizationToken ] [-SkipChec ### credential ``` -Connect-AutomateAPI [-AutomateCredentials ] [-Server ] [-TwoFactorToken ] +Connect-AutomateAPI [-Credentials ] [-Server ] [-TwoFactorToken ] [-Force] [-Quiet] [] ``` @@ -31,7 +31,7 @@ Connects to the Automate API and returns a bearer token which when passed with e ### EXAMPLE 1 ``` -Connect-AutomateAPI -Server "rancor.hostedrmm.com" -AutomateCredentials $CredentialObject -TwoFactorToken "999999" +Connect-AutomateAPI -Server "rancor.hostedrmm.com" -Credentials $CredentialObject -TwoFactorToken "999999" ``` ### EXAMPLE 2 @@ -41,7 +41,7 @@ Connect-AutomateAPI -Quiet ## PARAMETERS -### -AutomateCredentials +### -Credentials Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass ```yaml diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index fd12678..8bf95f5 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -6,7 +6,7 @@ Connect to the Automate API. Connects to the Automate API and returns a bearer token which when passed with each requests grants up to an hours worth of access. .PARAMETER Server The address to your Automate Server. Example 'rancor.hostedrmm.com' -.PARAMETER AutomateCredentials +.PARAMETER Credentials Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass .PARAMETER TwoFactorToken Takes a string that represents the 2FA number @@ -31,7 +31,7 @@ Author: Darren White Purpose/Change: Credential and 2FA prompting is only if needed. Supports Token Refresh. .EXAMPLE -Connect-AutomateAPI -Server "rancor.hostedrmm.com" -AutomateCredentials $CredentialObject -TwoFactorToken "999999" +Connect-AutomateAPI -Server "rancor.hostedrmm.com" -Credentials $CredentialObject -TwoFactorToken "999999" .EXAMPLE Connect-AutomateAPI -Quiet @@ -39,7 +39,7 @@ Connect-AutomateAPI -Quiet [CmdletBinding(DefaultParameterSetName = 'refresh')] param ( [Parameter(ParameterSetName = 'credential', Mandatory = $False)] - [System.Management.Automation.PSCredential]$AutomateCredentials, + [System.Management.Automation.PSCredential]$Credential, [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] @@ -51,6 +51,7 @@ Connect-AutomateAPI -Quiet [String]$AuthorizationToken = ($Script:CWAToken.Authorization -replace 'Bearer ',''), [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] + [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Switch]$SkipCheck, [Parameter(ParameterSetName = 'verify', Mandatory = $False)] @@ -72,7 +73,7 @@ Connect-AutomateAPI -Quiet # Check for locally stored credentials # [string]$CredentialDirectory = "$($env:USERPROFILE)\AutomateAPI\" # $LocalCredentialsExist = Test-Path "$($CredentialDirectory)Automate - Credentials.txt" - if ($TwoFactorToken -match '.+') {$Force=$True} + If ($TwoFactorToken -match '.+') {$Force=$True} $TwoFactorNeeded=$False If (!$Quiet) { @@ -85,14 +86,20 @@ Connect-AutomateAPI -Quiet } #End Begin Process { - if (!($Server -match '^[a-z0-9][a-z0-9\.\-]*$')) {throw "Server address is missing or in invalid format."; return} - if ($SkipCheck) { - #Build the returned token - $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $Null = $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") - Write-Debug "Setting Credentials to $($AutomateToken.Authorization)" + If (!($Server -match '^[a-z0-9][a-z0-9\.\-]*$')) {Throw "Server address is missing or in invalid format."; Return} + If ($SkipCheck) { $Script:CWAServer = ("https://" + $Server) - $Script:CWAToken = $AutomateToken + If ($Credential) { + Write-Debug "Setting Credentials to $($Credential.UserName)" + $Script:CWAToken = $AutomateToken + } + If ($AuthorizationToken) { + #Build the token + $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $Null = $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") + Write-Debug "Setting Authorization Token to $($AutomateToken.Authorization)" + $Script:CWAToken = $AutomateToken + } Return } If (!$AuthorizationToken -and $PSCmdlet.ParameterSetName -eq 'verify') { @@ -100,16 +107,16 @@ Connect-AutomateAPI -Quiet Return } Do { - Write-Debug "Starting Authorizaton Test." $AutomateAPIURI = ('https://' + $Server + '/cwa/api/v1') If (!$Quiet) { - If (!$AutomateCredentials -and ($Force -or !$AuthorizationToken)) { - If ($PSCmdlet.ParameterSetName -eq 'verify' -and $Script:CWACredentials) { - $AutomateCredentials = $Script:CWACredentials + If (!$Credential -and ($Force -or !$AuthorizationToken)) { + If (!$Force -and $Script:CWACredentials) { + $testCredentials = $Script:CWACredentials } Else { $Username = Read-Host -Prompt "Please enter your Automate Username" $Password = Read-Host -Prompt "Please enter your Automate Password" -AsSecureString - $AutomateCredentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + $Credential = New-Object System.Management.Automation.PSCredential ($Username, $Password) + $testCredentials=$Credential } } If ($TwoFactorNeeded -eq $True -and $TwoFactorToken -match '') { @@ -117,11 +124,11 @@ Connect-AutomateAPI -Quiet } } - If ($AutomateCredentials) { + If ($testCredentials) { #Build the headers for the Authentication $PostBody = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $PostBody.Add("username", $AutomateCredentials.UserName) - $PostBody.Add("password", $AutomateCredentials.GetNetworkCredential().Password) + $PostBody.Add("username", $testCredentials.UserName) + $PostBody.Add("password", $testCredentials.GetNetworkCredential().Password) If (!([string]::IsNullOrEmpty($TwoFactorToken))) { #Remove any spaces that were added $TwoFactorToken = $TwoFactorToken -replace '\s', '' @@ -157,55 +164,57 @@ Connect-AutomateAPI -Quiet $AutomateAPITokenResult = Invoke-RestMethod @RESTRequest } Catch { - Write-Debug "Request failed. Clearing Cached Token." $Script:CWAToken = $Null $Script:CWATokenKey = $Null - If ($AutomateCredentials -or $PSCmdlet.ParameterSetName -ne 'verify') { - Write-Debug "Request failed with credentials. Clearing Cached Credentials." + If ($testCredentials) { $Script:CWACredentials = $Null } - If ($AutomateCredentials) { + If ($Credential) { Throw "Attempt to authenticate to the Automate API has failed with error $_.Exception.Message" Return } } -# Write-Debug "Member Info: $(($AutomateAPITokenResult | Get-Member) -split '`n')" -# Write-Debug "JSON View: $(($AutomateAPITokenResult | ConvertTo-Json -Depth 100) -split '`n')" -# Write-Debug "Request Result: $($AutomateAPITokenResult|ConvertTo-JSON -Depth 5 -Compress)" $AuthorizationToken=$AutomateAPITokenResult.Accesstoken - $TwoFactorNeeded=$AutomateAPITokenResult.IsTwoFactorRequired - If ($PSCmdlet.ParameterSetName -eq 'verify' -and [string]::IsNullOrEmpty($AuthorizationToken)) { - Write-Debug "Re-Initializing AuthorizationToken with ""$($Script:CWAToken.Authorization)""" + If ($PSCmdlet.ParameterSetName -eq 'verify' -and !$AuthorizationToken -and $AutomateAPITokenResult) { $AuthorizationToken = $Script:CWAToken.Authorization -replace 'Bearer ','' } - Write-Debug "Authorization Token Value: ""$($AuthorizationToken)""" + $TwoFactorNeeded=$AutomateAPITokenResult.IsTwoFactorRequired } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationToken) -or - ($TwoFactorNeeded -ne $True -and $AutomateCredentials) -or + ($TwoFactorNeeded -ne $True -and $Credential) -or ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') ) } #End Process End { - If ([string]::IsNullOrEmpty($AuthorizationToken)) { + If ($SkipCheck) { + If ($Quiet) { + Return $False + } Else { + Return + } + } ElseIf ([string]::IsNullOrEmpty($AuthorizationToken)) { Throw "Unable to get Access Token. Either the credentials you entered are incorrect or you did not pass a valid two factor token" If ($Quiet) { Return $False + } Else { + Return } } Else { #Build the returned token $AutomateToken = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") - Write-Debug "Setting Credentials to $($AutomateToken.Authorization)" #Create Script Variables for this session in order to use the token + $Script:CWATokenKey = ConvertTo-SecureString $AuthorizationToken -AsPlainText -Force $Script:CWAServer = ("https://" + $Server) - If ($AutomateCredentials -or $PSCmdlet.ParameterSetName -ne 'verify') { - $Script:CWACredentials = $AutomateCredentials - $Script:CWATokenKey = ConvertTo-SecureString $AuthorizationToken -AsPlainText -Force + $Script:CWAToken = $AutomateToken + If ($Credential) { + $Script:CWACredentials = $Credential + } + If ($PSCmdlet.ParameterSetName -ne 'verify') { $AutomateAPITokenResult.PSObject.properties.remove('AccessToken') $Script:CWATokenInfo = $AutomateAPITokenResult } - $Script:CWAToken = $AutomateToken Write-Verbose "Token retrieved: $AuthorizationToken, expiration is $($Script:CWATokenInfo.ExpirationDate)" If (!$Quiet) { diff --git a/Public/Helpers/Set-CredentialsLocallyStored.ps1 b/Public/Helpers/Set-CredentialsLocallyStored.ps1 index 72a2daa..bc1983c 100644 --- a/Public/Helpers/Set-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Set-CredentialsLocallyStored.ps1 @@ -69,6 +69,7 @@ function Set-CredentialsLocallyStored { If (!(Get-Variable @SaveVar -ErrorAction 0)) {Continue} If ($SaveVar.Name -match 'Credential') { Try { + Write-Debug "Trying to save $($SaveVar.Name)" $x_Credential = @{'UserName'=(Get-Variable @SaveVar -ValueOnly).UserName; 'Password'=((Get-Variable @SaveVar -ValueOnly).Password|ConvertFrom-SecureString)} $Null = $StoreBlock | Add-Member -NotePropertyName $($SaveVar.Name) -NotePropertyValue $x_Credential } Catch { From 1a7b062867143fe923ba331e7e1bd874393d2f23 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 23:44:56 -0800 Subject: [PATCH 12/21] Enable LTPoSh override --- AutomateAPI.psm1 | 2 ++ Public/Repair-AutomateAgent.ps1 | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/AutomateAPI.psm1 b/AutomateAPI.psm1 index 28ccfab..aa3540d 100644 --- a/AutomateAPI.psm1 +++ b/AutomateAPI.psm1 @@ -9,4 +9,6 @@ $Private = @( Get-ChildItem -Recurse -Path "$PSScriptRoot\Private\" -filter *.ps } } +$Script:LTPoShURI='http://bit.ly/LTPoSh' + Export-ModuleMember -Function $Public.BaseName \ No newline at end of file diff --git a/Public/Repair-AutomateAgent.ps1 b/Public/Repair-AutomateAgent.ps1 index 41db66a..25bd553 100644 --- a/Public/Repair-AutomateAgent.ps1 +++ b/Public/Repair-AutomateAgent.ps1 @@ -27,6 +27,9 @@ function Repair-AutomateAgent { [int] $BatchSize = 5, + [Parameter(Mandatory = $False)] + [String]$LTPoShURI = $Script:LTPoShURI, + [Parameter(ValueFromPipeline = $true)] $AutomateControlStatusObject ) @@ -67,25 +70,25 @@ function Repair-AutomateAgent { If ($Action -eq 'Check') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - Check Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $($using:ControlServer) -Credential $($using:ControlAPICredentials) -GUID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('http://bit.ly/LTPoSh') | iex; Get-LTServiceInfo" -TimeOut 60000 -MaxLength 10240 + $ServiceRestartAttempt = Invoke-ControlCommand -Server $($using:ControlServer) -Credential $($using:ControlAPICredentials) -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; Get-LTServiceInfo" -TimeOut 60000 -MaxLength 10240 return $ServiceRestartAttempt } | out-null } ElseIf ($Action -eq 'Update') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - Update Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -GUID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('http://bit.ly/LTPoSh') | iex; Update-LTService" -TimeOut 120000 -MaxLength 10240 + $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; Update-LTService" -TimeOut 120000 -MaxLength 10240 return $ServiceRestartAttempt } | out-null } ElseIf ($Action -eq 'Restart') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - Restart Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -GUID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('http://bit.ly/LTPoSh') | iex; Restart-LTService" -TimeOut 120000 -MaxLength 10240 + $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; Restart-LTService" -TimeOut 120000 -MaxLength 10240 return $ServiceRestartAttempt } | out-null } ElseIf ($Action -eq 'Reinstall') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - ReInstall Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -GUID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('http://bit.ly/LTPoSh') | iex; ReInstall-LTService" -TimeOut 360000 -MaxLength 10240 + $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; ReInstall-LTService" -TimeOut 360000 -MaxLength 10240 return $ServiceRestartAttempt } | out-null } Else { From cbd91afe2a6d205950beded582666a2fb8356b47 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 23:52:16 -0800 Subject: [PATCH 13/21] Introducing Upgraded Invoke-ControlCommand.ps1 --- Public/Invoke-ControlCommand.ps1 | 208 ++++++++++++++++++------------ Public/Invoke-ControlCommand2.ps1 | 164 ----------------------- 2 files changed, 129 insertions(+), 243 deletions(-) delete mode 100644 Public/Invoke-ControlCommand2.ps1 diff --git a/Public/Invoke-ControlCommand.ps1 b/Public/Invoke-ControlCommand.ps1 index a577e49..7b48272 100644 --- a/Public/Invoke-ControlCommand.ps1 +++ b/Public/Invoke-ControlCommand.ps1 @@ -4,9 +4,10 @@ function Invoke-ControlCommand { Will issue a command against a given machine and return the results. .DESCRIPTION Will issue a command against a given machine and return the results. - .PARAMETER GUID + .PARAMETER SessionID The GUID identifier for the machine you wish to connect to. You can retrieve session info with the 'Get-CWCSessions' commandlet + SessionIDs can be provided via the pipeline. .PARAMETER Command The command you wish to issue to the machine. .PARAMETER TimeOut @@ -18,114 +19,163 @@ function Invoke-ControlCommand { .OUTPUTS The output of the Command provided. .NOTES - Version: 1.0 + Version: 2.0 Author: Chris Taylor Modified By: Gavin Stone + Modified By: Darren White Creation Date: 1/20/2016 Purpose/Change: Initial script development + + Update Date: 2019-02-19 + Author: Darren White + Purpose/Change: Enable Pipeline support. Enable processing using Automate Control Extension. The cached APIKey will be used if present. + .EXAMPLE - Invoke-ControlCommand -GUID $GUID -Command 'hostname' + Invoke-ControlCommand -SessionID $SessionID -Command 'hostname' Will return the hostname of the machine. .EXAMPLE - Invoke-ControlCommand -GUID $GUID -User $User -Password $Password -TimeOut 120000 -Command 'iwr -UseBasicParsing "https://bit.ly/ltposh" | iex; Restart-LTService' -PowerShell + Invoke-ControlCommand -SessionID $SessionID -User $User -Password $Password -TimeOut 120000 -Command 'iwr -UseBasicParsing "https://bit.ly/ltposh" | iex; Restart-LTService' -PowerShell Will restart the Automate agent on the target machine. #> [CmdletBinding()] param ( [string]$Server = $Script:ControlServer, [System.Management.Automation.PSCredential]$Credentials = $Script:ControlAPICredentials, - [Parameter(Mandatory=$True)] - [guid]$GUID, + [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)] + [guid[]]$SessionID, [string]$Command, [int]$TimeOut = 10000, [switch]$PowerShell, - [string]$Group = "All Machines", [int]$MaxLength = 5000 ) - $Server = $Server -replace '/$','' - If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} - $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 + Begin { + $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 + $Server = $Server -replace '/$','' - $URI = "$Server/Services/PageService.ashx/AddEventToSessions" - - # Format command - $FormattedCommand = @() - if ($Powershell) { - $FormattedCommand += '#!ps' + # Format command + $FormattedCommand = @() + if ($Powershell) { + $FormattedCommand += '#!ps' + } + $FormattedCommand += "#timeout=$TimeOut" + $FormattedCommand += "#maxlength=$MaxLength" + $FormattedCommand += $Command + $FormattedCommand = $FormattedCommand | Out-String + $SessionEventType = 44 } - $FormattedCommand += "#timeout=$TimeOut" - $FormattedCommand += "#maxlength=$MaxLength" - $FormattedCommand += $Command - $FormattedCommand = $FormattedCommand | Out-String - $SessionEventType = 44 - $Body = ConvertTo-Json @($Group,@($GUID),$SessionEventType,$FormattedCommand) - Write-Verbose $Body - - # Issue command - try { - $null = Invoke-RestMethod -Uri $URI -Method Post -Credential $Credentials -ContentType "application/json" -Body $Body - } - catch { - Write-Error "$(($_.ErrorDetails | ConvertFrom-Json).message)" - return - } + Process { + If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} + ForEach ($GUID IN $SessionID) { + $Body = ConvertTo-Json @("",@($GUID),$SessionEventType,$FormattedCommand) -Compress + Write-Verbose $Body - # Get Session - $URI = "$Server/Services/PageService.ashx/GetSessionDetails" - $Body = ConvertTo-Json @($Group,$GUID) - Write-Verbose $Body - try { - $SessionDetails = Invoke-RestMethod -Uri $URI -Method Post -Credential $Credentials -ContentType "application/json" -Body $Body - } - catch { - Write-Error $($_.Exception.Message) - return - } + $RESTRequest = @{ + 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7//ReplicaService.ashx/PageAddEventToSessions" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } + + # Issue command + try { + $null = Invoke-RestMethod @RESTRequest + } + catch { + Write-Error "$(($_.ErrorDetails | ConvertFrom-Json).message)" + return + } - #Get time command was executed - $epoch = $((New-TimeSpan -Start $(Get-Date -Date "01/01/1970") -End $(Get-Date)).TotalSeconds) - $ExecuteTime = $epoch - ((($SessionDetails.events | Where-Object {$_.EventType -eq 44})[-1]).Time /1000) - $ExecuteDate = $origin.AddSeconds($ExecuteTime) + #Get the timestamp for the Queued command. + $Body=ConvertTo-Json @("SessionEvent",@("SessionID"),@("LastTime"),"sessionid='$GUID' AND EventType='QueuedCommand'","",10) -Compress + $RESTRequest = @{ + 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } - # Look for results of command - $Looking = $True - $TimeOutDateTime = (Get-Date).AddMilliseconds($TimeOut) - $Body = ConvertTo-Json @($Group,$GUID) - while ($Looking) { - try { - $SessionDetails = Invoke-RestMethod -Uri $URI -Method Post -Credential $Credentials -ContentType "application/json" -Body $Body - } - catch { - Write-Error $($_.Exception.Message) - return - } + # Get Session + Write-Verbose $Body + try { + $SessionDetails = Invoke-RestMethod @RESTRequest - $ConnectionsWithData = @() - Foreach ($Connection in $SessionDetails.connections) { - $ConnectionsWithData += $Connection | Where-Object {$_.Events.EventType -eq 70} - } + $FNames=$SessionDetails.FieldNames + $SCRecords=($SessionDetails.Items | ForEach-Object { + $x=$_ + $SCEventRecord = [pscustomobject]@{} + for($i=0; $i -lt $FNames.Length; $i++){ + $Null = $SCEventRecord | Add-Member -NotePropertyName $FNames[$i] -NotePropertyValue $x[$i] + } + $SCEventRecord + }) + $EventDate=(Get-Date ($SCRecords | Select-Object -Expand LastTime) -UFormat "%Y-%m-%d %T") + } + catch { + Write-Error $($_.Exception.Message) + return + } + + # Look for results of command + $Body=ConvertTo-Json @("SessionConnectionEvent",@(),@("SessionID","Time","Data"),"SessionID='$GUID' AND EventType='RanCommand' AND Time>='$EventDate'","",100) -Compress + $RESTRequest = @{ + 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" + 'Method' = 'POST' + 'ContentType' = 'application/json' + 'Body' = $Body + } + + If ($Script:ControlAPIKey) { + $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) + } Else { + $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) + } - $Events = ($ConnectionsWithData.events | Where-Object {$_.EventType -eq 70 -and $_.Time}) - foreach ($Event in $Events) { - $epoch = $((New-TimeSpan -Start $(Get-Date -Date "01/01/1970") -End $(Get-Date)).TotalSeconds) - $CheckTime = $epoch - ($Event.Time /1000) - $CheckDate = $origin.AddSeconds($CheckTime) - if ($CheckDate -gt $ExecuteDate) { - $Looking = $False - $Output = $Event.Data -split '[\r\n]' | Where-Object {$_} - if(!$PowerShell){ - $Output = $Output | Select-Object -skip 1 + $Looking = $True + $TimeOutDateTime = (Get-Date).AddMilliseconds($TimeOut) + while ($Looking) { + Start-Sleep -Seconds 1 + try { + $SessionEvents = Invoke-RestMethod @RESTRequest + } + catch { + Write-Error $($_.Exception.Message) + return + } + + $FNames=$SessionEvents.FieldNames + $Events = ($SessionEvents.Items | ForEach-Object {$x=$_; $SCEventRecord = [pscustomobject]@{}; for($i=0; $i -lt $FNames.Length; $i++){$Null = $SCEventRecord | Add-Member -NotePropertyName $FNames[$i] -NotePropertyValue $x[$i]}; $SCEventRecord}) + foreach ($Event in $Events) { + if ($Event.Time -ge $EventDate) { + $Looking = $False + $Output = $Event.Data -split '[\r\n]' | Where-Object {$_} + if(!$PowerShell){ + $Output = $Output | Select-Object -skip 1 + } + return $Output + } + } + + if ($Looking -and $(Get-Date) -gt $TimeOutDateTime.AddSeconds(1)) { + $Looking = $False + $Output = "Command timed out when sent to Agent" } - return $Output } } + } - Start-Sleep -Seconds 1 - if ($(Get-Date) -gt $TimeOutDateTime.AddSeconds(1)) { - $Looking = $False - $Output = "Command timed out when sent to Agent" - } + End { + } } \ No newline at end of file diff --git a/Public/Invoke-ControlCommand2.ps1 b/Public/Invoke-ControlCommand2.ps1 deleted file mode 100644 index fab97c3..0000000 --- a/Public/Invoke-ControlCommand2.ps1 +++ /dev/null @@ -1,164 +0,0 @@ -function Invoke-ControlCommand2 { - <# - .SYNOPSIS - Will issue a command against a given machine and return the results. - .DESCRIPTION - Will issue a command against a given machine and return the results. - .PARAMETER GUID - The GUID identifier for the machine you wish to connect to. - You can retrieve session info with the 'Get-CWCSessions' commandlet - .PARAMETER Command - The command you wish to issue to the machine. - .PARAMETER TimeOut - The amount of time in milliseconds that a command can execute. The default is 10000 milliseconds. - .PARAMETER PowerShell - Issues the command in a powershell session. - .PARAMETER Group - Name of session group to use. - .OUTPUTS - The output of the Command provided. - .NOTES - Version: 1.0 - Author: Chris Taylor - Modified By: Gavin Stone - Creation Date: 1/20/2016 - Purpose/Change: Initial script development - .EXAMPLE - Invoke-ControlCommand -GUID $GUID -Command 'hostname' - Will return the hostname of the machine. - .EXAMPLE - Invoke-ControlCommand -GUID $GUID -User $User -Password $Password -TimeOut 120000 -Command 'iwr -UseBasicParsing "https://bit.ly/ltposh" | iex; Restart-LTService' -PowerShell - Will restart the Automate agent on the target machine. - #> - [CmdletBinding()] - param ( - [string]$Server = $Script:ControlServer, - [System.Management.Automation.PSCredential]$Credentials = $Script:ControlAPICredentials, - [Parameter(Mandatory=$True)] - [guid]$GUID, - [string]$Command, - [int]$TimeOut = 10000, - [switch]$PowerShell, - [int]$MaxLength = 5000 - ) - - $Server = $Server -replace '/$','' - If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} - $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 - - # Format command - $FormattedCommand = @() - if ($Powershell) { - $FormattedCommand += '#!ps' - } - $FormattedCommand += "#timeout=$TimeOut" - $FormattedCommand += "#maxlength=$MaxLength" - $FormattedCommand += $Command - $FormattedCommand = $FormattedCommand | Out-String - - $SessionEventType = 44 - $Body = ConvertTo-Json @("",@($GUID),$SessionEventType,$FormattedCommand) -Compress - Write-Verbose $Body - - $RESTRequest = @{ - 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7//ReplicaService.ashx/PageAddEventToSessions" - 'Method' = 'POST' - 'ContentType' = 'application/json' - 'Body' = $Body - } - If ($Script:ControlAPIKey) { - $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) - } Else { - $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) - } - - # Issue command - try { - $null = Invoke-RestMethod @RESTRequest - } - catch { - Write-Error "$(($_.ErrorDetails | ConvertFrom-Json).message)" - return - } - - #Get the timestamp for the Queued command. - $Body=ConvertTo-Json @("SessionEvent",@("SessionID"),@("LastTime"),"sessionid='$GUID' AND EventType='QueuedCommand'","",10) -Compress - $RESTRequest = @{ - 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" - 'Method' = 'POST' - 'ContentType' = 'application/json' - 'Body' = $Body - } - If ($Script:ControlAPIKey) { - $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) - } Else { - $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) - } - - # Get Session - Write-Verbose $Body - try { - $SessionDetails = Invoke-RestMethod @RESTRequest - - $FNames=$SessionDetails.FieldNames - $SCRecords=($SessionDetails.Items | ForEach-Object { - $x=$_ - $SCEventRecord = [pscustomobject]@{} - for($i=0; $i -lt $FNames.Length; $i++){ - $Null = $SCEventRecord | Add-Member -NotePropertyName $FNames[$i] -NotePropertyValue $x[$i] - } - $SCEventRecord - }) - $EventDate=(Get-Date ($SCRecords | Select-Object -Expand LastTime) -UFormat "%Y-%m-%d %T") - } - catch { - Write-Error $($_.Exception.Message) - return - } - - # Look for results of command - $Body=ConvertTo-Json @("SessionConnectionEvent",@(),@("SessionID","Time","Data"),"SessionID='$GUID' AND EventType='RanCommand' AND Time>='$EventDate'","",100) -Compress - $RESTRequest = @{ - 'URI' = "$Server/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" - 'Method' = 'POST' - 'ContentType' = 'application/json' - 'Body' = $Body - } - - If ($Script:ControlAPIKey) { - $RESTRequest.Add('Headers',@{'CWAIKToken' = (Get-CWAIKToken)}) - } Else { - $RESTRequest.Add('Credential',${Script:ControlAPICredentials}) - } - - $Looking = $True - $TimeOutDateTime = (Get-Date).AddMilliseconds($TimeOut) - while ($Looking) { - Start-Sleep -Seconds 1 - try { - $SessionEvents = Invoke-RestMethod @RESTRequest - } - catch { - Write-Error $($_.Exception.Message) - return - } - - $FNames=$SessionEvents.FieldNames - $Events = ($SessionEvents.Items | ForEach-Object {$x=$_; $SCEventRecord = [pscustomobject]@{}; for($i=0; $i -lt $FNames.Length; $i++){$Null = $SCEventRecord | Add-Member -NotePropertyName $FNames[$i] -NotePropertyValue $x[$i]}; $SCEventRecord}) - foreach ($Event in $Events) { - if ($Event.Time -ge $EventDate) { - $Looking = $False - $Output = $Event.Data -split '[\r\n]' | Where-Object {$_} - if(!$PowerShell){ - $Output = $Output | Select-Object -skip 1 - } - return $Output - } - } - - if ($Looking -and $(Get-Date) -gt $TimeOutDateTime.AddSeconds(1)) { - $Looking = $False - $Output = "Command timed out when sent to Agent" - } - } -} \ No newline at end of file From 237309f46c290ef3afa5b4e1926999cd2bc86243 Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 23:55:45 -0800 Subject: [PATCH 14/21] Debugging Output and Variable Clearing Fix --- Private/Get-CWAIKToken.ps1 | 5 +++ Public/Connect-AutomateAPI.ps1 | 6 ++-- Public/Connect-ControlAPI.ps1 | 63 +++++++++++++++++++++------------- Public/Get-ControlSessions.ps1 | 8 +++-- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/Private/Get-CWAIKToken.ps1 b/Private/Get-CWAIKToken.ps1 index 74a363d..49588a8 100644 --- a/Private/Get-CWAIKToken.ps1 +++ b/Private/Get-CWAIKToken.ps1 @@ -21,5 +21,10 @@ function Get-CWAIKToken { If ($hmacsha) { $Local:CWAIKToken = [Convert]::ToBase64String($hmacsha.ComputeHash($barray)) } + If ($Local:CWAIKToken) { + Write-Debug "Generated CWAIKToken ""$($Local:CWAIKToken)""" + } Else { + Write-Debug "Error. CWAIKToken was not generated using APIKey $APIKey." + } Return $Local:CWAIKToken } \ No newline at end of file diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index 8bf95f5..8cee38a 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -164,10 +164,9 @@ Connect-AutomateAPI -Quiet $AutomateAPITokenResult = Invoke-RestMethod @RESTRequest } Catch { - $Script:CWAToken = $Null - $Script:CWATokenKey = $Null + Remove-Variable CWAToken,CWATokenKey -Scope Script -ErrorAction 0 If ($testCredentials) { - $Script:CWACredentials = $Null + Remove-Variable CWACredentials -Scope Script -ErrorAction 0 } If ($Credential) { Throw "Attempt to authenticate to the Automate API has failed with error $_.Exception.Message" @@ -194,6 +193,7 @@ Connect-AutomateAPI -Quiet Return } } ElseIf ([string]::IsNullOrEmpty($AuthorizationToken)) { + Remove-Variable CWAToken -Scope Script -ErrorAction 0 Throw "Unable to get Access Token. Either the credentials you entered are incorrect or you did not pass a valid two factor token" If ($Quiet) { Return $False diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index c6fb914..d01e95b 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -10,7 +10,8 @@ function Connect-ControlAPI { Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass .PARAMETER APIKey Automate APIKey for Control Extension - + .PARAMETER Verify + Attempt to verify Cached API key or Credentials. Invalid results will be removed. .PARAMETER Quiet Will not output any standard logging messages .OUTPUTS @@ -29,15 +30,17 @@ function Connect-ControlAPI { [CmdletBinding(DefaultParameterSetName = 'refresh')] param ( [Parameter(ParameterSetName = 'credential', Mandatory = $False)] - [System.Management.Automation.PSCredential]$Credentials, + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] + [System.Management.Automation.PSCredential]$Credential, [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] [String]$Server = $Script:ControlServer, [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] - $APIKey = ($Script:ControlAPIKey), + $APIKey = ([SecureString]$Script:ControlAPIKey), # [Parameter(ParameterSetName = 'credential', Mandatory = $False)] # [String]$TwoFactorToken, @@ -45,12 +48,16 @@ function Connect-ControlAPI { [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Switch]$Force, + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] + [Switch]$Verify, + [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Switch]$SkipCheck, [Parameter(ParameterSetName = 'credential', Mandatory = $False)] [Parameter(ParameterSetName = 'refresh', Mandatory = $False)] [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] + [Parameter(ParameterSetName = 'verify', Mandatory = $False)] [Switch]$Quiet ) @@ -70,13 +77,14 @@ function Connect-ControlAPI { Process { If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} - If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and !($Null -eq $Credentials)) { - Write-Debug "Skipping validation. Setting Server=$($Server) and Credentials=$($Credentials.Username)" - $Script:ControlAPICredentials = $Credentials + If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and !($Null -eq $Credential)) { + Write-Debug "Skipping validation. Setting Server=$($Server) and Credential=$($Credential.Username)" + $Script:ControlAPICredentials = $Credential $Script:ControlServer = $Server Return - } ElseIf ($PSCmdlet.ParameterSetName -eq 'apikey' -and $Null -ne $APIKey) { - If ($APIKey -notmatch '[a-f0-9]{100,}') { + } + If (($PSCmdlet.ParameterSetName -eq 'apikey' -or $PSCmdlet.ParameterSetName -eq 'verify') -and $Null -ne $APIKey) { + If ($APIKey.GetType() -notmatch 'SecureString') { [SecureString]$APIKey = ConvertTo-SecureString $APIKey -AsPlainText -Force } If (!$SkipCheck) { @@ -90,31 +98,36 @@ function Connect-ControlAPI { Try { $AuthorizationResult = Invoke-RestMethod @RESTRequest } Catch { - $Script:ControlAPIKey = $Null + Write-Debug "Result: $($AuthorizationResult | Select-Object -Property * | ConvertTo-Json -Depth 10 -Compress)" + Remove-Variable ControlAPIKey -Scope Script -ErrorAction 0 $APIKey=$Null Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" - Return + If ($Quiet) { + Return $False + } Else { + Return + } } } Return } Else { If (!$testCredentials -and !$Force) { - If (!$Credentials) { + If (!$Credential) { $testCredentials = $Script:ControlAPICredentials } Else { - $testCredentials = $Credentials + $testCredentials = $Credential } } Do { $ControlAPITestURI = ($Server + '/Services/PageService.ashx/GetHostSessionInfo') If (!$Quiet) { - If (!$Credentials) { + If (!$Credential) { If (!$testCredentials -or $Force) { Write-Debug "No Credentials were provided and no existing Token was found, or -Force was specified" $testCredentials = $Null $Username = Read-Host -Prompt "Please enter your Control Username" $Password = Read-Host -Prompt "Please enter your Control Password" -AsSecureString - $Credentials = New-Object System.Management.Automation.PSCredential ($Username, $Password) + $Credential = New-Object System.Management.Automation.PSCredential ($Username, $Password) } Else { $Force = $True } @@ -125,7 +138,7 @@ function Connect-ControlAPI { } } - If (!$testCredentials) {$testCredentials=$Credentials} + If (!$testCredentials) {$testCredentials=$Credential} #Invoke the REST Method $RESTRequest = @{ 'URI' = $ControlAPITestURI @@ -138,17 +151,21 @@ function Connect-ControlAPI { $ControlAPITokenResult = Invoke-RestMethod @RESTRequest } Catch { - $Script:ControlAPICredentials = $Null - If ($Credentials) { + Remove-Variable ControlAPICredentials -Scope Script -ErrorAction 0 + If ($Credential) { Throw "Unable to connect to Control. Server Address or Control Credentials are wrong. This module does not support 2FA for Control Users" - Return + If ($Quiet) { + Return $False + } Else { + Return + } } } Write-Debug "Request Results: $($ControlAPITokenResult|ConvertTo-Json -Depth 5 -Compress)" $AuthorizationResult=$ControlAPITokenResult.ProductVersion $TwoFactorNeeded=$ControlAPITokenResult.IsTwoFactorRequired } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationResult) -or - ($TwoFactorNeeded -ne $True -and $Credentials) -or + ($TwoFactorNeeded -ne $True -and $Credential) -or ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') ) } @@ -158,12 +175,12 @@ function Connect-ControlAPI { If ($SkipCheck) { If ($Quiet) {Return $True} Else { - Write-Host -BackgroundColor Green -ForegroundColor Black "Skipping validation. Setting Server=$($Server) and Credentials=$($Credentials.Username)" + Write-Host -BackgroundColor Green -ForegroundColor Black "Skipping validation. Setting Server=$($Server) and Credentials=$($Credential.Username)" } Return } If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey)) { - $Script:ControlAPIKey = $Null + Remove-Variable ControlAPIKey -Scope Script -ErrorAction 0 Throw "Unable to validate the APIKey provided." If ($Quiet) { Return $False @@ -171,8 +188,8 @@ function Connect-ControlAPI { Return } } ElseIf ($PSCmdlet.ParameterSetName -ne 'apikey' -and [string]::IsNullOrEmpty($AuthorizationResult)) { - $Script:ControlAPICredentials = $Null - Throw "Unable to get Access Token. Either the credentials your entered are incorrect or you did not pass a valid two factor token" + Remove-Variable ControlAPICredentials -Scope Script -ErrorAction 0 + Throw "Unable to get Access Token. Either the credentials provided are incorrect or you did not pass a valid two factor token" If ($Quiet) { Return $False } Else { diff --git a/Public/Get-ControlSessions.ps1 b/Public/Get-ControlSessions.ps1 index d4905f1..1ad0817 100644 --- a/Public/Get-ControlSessions.ps1 +++ b/Public/Get-ControlSessions.ps1 @@ -22,7 +22,7 @@ function Get-ControlSessions { } end { - $Body=ConvertTo-Json @("SessionConnectionEvent",@("SessionID","EventType"),@("LastTime"),"SessionConnectionProcessType=""Guest"" AND (EventType = ""Connected"" OR EventType = ""Disconnected"")", "", 20000) -Compress + $Body=ConvertTo-Json @("SessionConnectionEvent",@("SessionID","EventType"),@("LastTime"),"SessionConnectionProcessType='Guest' AND (EventType = 'Connected' OR EventType = 'Disconnected')", "", 20000) -Compress $RESTRequest = @{ 'URI' = "${Script:ControlServer}/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/ReportService.ashx/GenerateReportForAutomate" 'Method' = 'POST' @@ -36,8 +36,10 @@ function Get-ControlSessions { } $SCConnected = @{}; + Write-Debug "Submitting Request to $($RESTRequest.URI)`nHeaders:`n$($RESTRequest.Headers|ConvertTo-JSON -Depth 5)`nBody:`n$($RESTRequest.Body|ConvertTo-JSON -Depth 5)" Try { $SCData = Invoke-RestMethod @RESTRequest + Write-Debug "Request Result: $($SCData | select-object -property * | convertto-json -Depth 10)" If ($SCData.FieldNames -contains 'SessionID' -and $SCData.FieldNames -contains 'EventType' -and $SCData.FieldNames -contains 'LastTime') { $AllData = $SCData.Items.GetEnumerator() | select-object @{Name='SessionID'; Expression={$_[0]}},@{Name='Event'; Expression={$_[1]}},@{Name='Date'; Expression={$_[2]}} | sort-Object SessionID,Event -Descending; $AllData | ForEach-Object { @@ -64,7 +66,9 @@ function Get-ControlSessions { Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" Return } - } Catch { } + } Catch { + Write-Debug "FAILED! Request Result: $($SCData | select-object -property * | convertto-json -Depth 10)" + } return $SCConnected } } From 23c8d81bfffab45ee9ecbc3df229e218940b064a Mon Sep 17 00:00:00 2001 From: Darren White Date: Tue, 19 Feb 2019 23:56:24 -0800 Subject: [PATCH 15/21] Validate Automate and Control cached information after loading. --- Public/Helpers/Get-CredentialsLocallyStored.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Public/Helpers/Get-CredentialsLocallyStored.ps1 b/Public/Helpers/Get-CredentialsLocallyStored.ps1 index 3ec300c..29f1ea6 100644 --- a/Public/Helpers/Get-CredentialsLocallyStored.ps1 +++ b/Public/Helpers/Get-CredentialsLocallyStored.ps1 @@ -60,6 +60,9 @@ function Get-CredentialsLocallyStored { $AutomateToken.Add("Authorization", "Bearer $AuthorizationToken") $Script:CWAToken = $AutomateToken } + If (!(Connect-AutomateAPI -Verify -Quiet -ErrorAction 0)) { + Write-Error "Automate Credentials failed to successfully validate. Call Connect-AutomateAPI to establish a valid session." -ErrorAction 'Continue' + } } If ($Control) { @@ -92,6 +95,9 @@ function Get-CredentialsLocallyStored { $Null = Set-Variable @SaveVar -Value $($StoreBlock.$($SaveVar.Name)) } } + If (!(Connect-ControlAPI -Verify -Quiet -ErrorAction 0)) { + Write-Error "Control Credentials failed to successfully validate. Call Connect-ControlAPI to establish a valid session." -ErrorAction 'Continue' + } } If ($Custom) { From c0126795eabb8b2feeb9f0de3f7712a39dc66313 Mon Sep 17 00:00:00 2001 From: Darren White Date: Wed, 20 Feb 2019 00:02:00 -0800 Subject: [PATCH 16/21] Version Increment --- AutomateAPI.psd1 | Bin 7940 -> 7940 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/AutomateAPI.psd1 b/AutomateAPI.psd1 index f9b22fa497b58e62df65a5b50dbad134ec2c1aaf..660474126ab90109728f10f160c8e082c07dd56e 100644 GIT binary patch delta 14 VcmZp%Yq8s4!o+B{*_7$L3;-i&1a|-c delta 14 VcmZp%Yq8s4!o+C0*_7$L3;-iy1a<%b From 4b3d80db4ed822f532e431b191ea8a1062528522 Mon Sep 17 00:00:00 2001 From: Darren White Date: Wed, 20 Feb 2019 10:11:54 -0800 Subject: [PATCH 17/21] SkipCheck parameter update --- Public/Connect-ControlAPI.ps1 | 80 ++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index d01e95b..900d21a 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -52,6 +52,7 @@ function Connect-ControlAPI { [Switch]$Verify, [Parameter(ParameterSetName = 'credential', Mandatory = $False)] + [Parameter(ParameterSetName = 'apikey', Mandatory = $False)] [Switch]$SkipCheck, [Parameter(ParameterSetName = 'credential', Mandatory = $False)] @@ -77,36 +78,31 @@ function Connect-ControlAPI { Process { If (!($Server -match 'https?://[a-z0-9][a-z0-9\.\-]*(:[1-9][0-9]*)?$')) {throw "Control Server address is in invalid format."; return} - If ($SkipCheck -and $PSCmdlet.ParameterSetName -eq 'credential' -and !($Null -eq $Credential)) { - Write-Debug "Skipping validation. Setting Server=$($Server) and Credential=$($Credential.Username)" - $Script:ControlAPICredentials = $Credential - $Script:ControlServer = $Server + If ($SkipCheck) { Return } If (($PSCmdlet.ParameterSetName -eq 'apikey' -or $PSCmdlet.ParameterSetName -eq 'verify') -and $Null -ne $APIKey) { If ($APIKey.GetType() -notmatch 'SecureString') { [SecureString]$APIKey = ConvertTo-SecureString $APIKey -AsPlainText -Force } - If (!$SkipCheck) { - # Retrieve Control Instance ID to verify APIKey - $RESTRequest = @{ - 'Method' = 'GET' - 'URI' = "$($Server)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/Service.ashx/GetServerVersion" - 'Headers' = @{'CWAIKToken' = (Get-CWAIKToken -APIKey $APIKey)} - } - Write-Debug "Submitting Request to $($RESTRequest.URI)" - Try { - $AuthorizationResult = Invoke-RestMethod @RESTRequest - } Catch { - Write-Debug "Result: $($AuthorizationResult | Select-Object -Property * | ConvertTo-Json -Depth 10 -Compress)" - Remove-Variable ControlAPIKey -Scope Script -ErrorAction 0 - $APIKey=$Null - Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" - If ($Quiet) { - Return $False - } Else { - Return - } + # Retrieve Control Instance ID to verify APIKey + $RESTRequest = @{ + 'Method' = 'GET' + 'URI' = "$($Server)/App_Extensions/fc234f0e-2e8e-4a1f-b977-ba41b14031f7/Service.ashx/GetServerVersion" + 'Headers' = @{'CWAIKToken' = (Get-CWAIKToken -APIKey $APIKey)} + } + Write-Debug "Submitting Request to $($RESTRequest.URI)" + Try { + $AuthorizationResult = Invoke-RestMethod @RESTRequest + } Catch { + Write-Debug "Result: $($AuthorizationResult | Select-Object -Property * | ConvertTo-Json -Depth 10 -Compress)" + Remove-Variable ControlAPIKey -Scope Script -ErrorAction 0 + $APIKey=$Null + Throw "Attempt to authenticate the Control API Key has failed with error $_.Exception.Message" + If ($Quiet) { + Return $False + } Else { + Return } } Return @@ -173,13 +169,37 @@ function Connect-ControlAPI { End { If ($SkipCheck) { - If ($Quiet) {Return $True} - Else { - Write-Host -BackgroundColor Green -ForegroundColor Black "Skipping validation. Setting Server=$($Server) and Credentials=$($Credential.Username)" + If ($PSCmdlet.ParameterSetName -eq 'apikey' -and !($Null -eq $APIKey)) { + If ($APIKey.GetType() -notmatch 'SecureString') { + [SecureString]$APIKey = ConvertTo-SecureString $APIKey -AsPlainText -Force + } + Write-Debug "Skipping validation. Setting Server=$($Server) and APIKey." + $Script:ControlServer = $Server + $Script:ControlAPIKey = $APIKey + If ($Quiet) { + Return $True + } Else { + Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully stored the Server and APIKey values." + Return + } + } ElseIf ($PSCmdlet.ParameterSetName -eq 'credential' -and !($Null -eq $Credential)) { + Write-Debug "Skipping validation. Setting Server=$($Server) and Credential=$($Credential.Username)" + $Script:ControlServer = $Server + $Script:ControlAPICredentials = $Credential + If ($Quiet) { + Return $True + } Else { + Write-Host -BackgroundColor Green -ForegroundColor Black "Successfully stored the Server and Credential values." + Return + } } - Return - } - If (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey)) { + Throw "SkipCheck failed because the Server, APIKey, or Credentials were not provided." + If ($Quiet) { + Return $False + } Else { + Return + } + } ElseIf (($PSCmdlet.ParameterSetName -eq 'apikey' -and !$APIKey)) { Remove-Variable ControlAPIKey -Scope Script -ErrorAction 0 Throw "Unable to validate the APIKey provided." If ($Quiet) { From b79785eb08a16c3bf6cafc4259010f3ff4500e7f Mon Sep 17 00:00:00 2001 From: Darren White Date: Wed, 20 Feb 2019 10:27:06 -0800 Subject: [PATCH 18/21] Updated Connection Options to support APIKey and Credentials --- Public/Repair-AutomateAgent.ps1 | 56 ++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/Public/Repair-AutomateAgent.ps1 b/Public/Repair-AutomateAgent.ps1 index 25bd553..26e1f8a 100644 --- a/Public/Repair-AutomateAgent.ps1 +++ b/Public/Repair-AutomateAgent.ps1 @@ -38,15 +38,26 @@ function Repair-AutomateAgent { $ResultArray = @() $ObjectCapture = @() $null = Get-RSJob | Remove-RSJob | Out-Null - $ControlAPICredentials = $Script:ControlAPICredentials $ControlServer = $Script:ControlServer - If (!($ControlServer -and $ControlAPICredentials)) { - Throw "Control Server information must be assigned with Connect-ControlAPI function first." - Continue - } + $ControlAPIKey = $Script:ControlAPIKey + $ControlAPICredentials = $Script:ControlAPICredentials + $ConnectOptions=$Null } Process { + If ($ControlServer -and $ControlAPIKey) { + $ConnectOptions = @{ + 'Server' = $ControlServer + 'APIKey' = $ControlAPIKey + } + } ElseIf ($ControlServer -and $ControlAPICredentials) { + $ConnectOptions = @{ + 'Server' = $ControlServer + 'Credential' = $ControlAPICredentials + } + } Else { + Return + } Foreach ($igu in $AutomateControlStatusObject) { If ($igu.ComputerID -and $igu.SessionID) { If ($PSCmdlet.ShouldProcess("Automate Services on $($igu.ComputerID) - $($igu.ComputerName)",$Action)) { @@ -64,32 +75,47 @@ function Repair-AutomateAgent { } End { - Write-Host -ForegroundColor Green "Starting fixes" + If (!$ConnectOptions) { + Throw "Control Server information must be assigned with Connect-ControlAPI function first." + Return + } if ($ObjectCapture) { - + Write-Host -ForegroundColor Green "Starting fixes" If ($Action -eq 'Check') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - Check Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $($using:ControlServer) -Credential $($using:ControlAPICredentials) -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; Get-LTServiceInfo" -TimeOut 60000 -MaxLength 10240 - return $ServiceRestartAttempt + $ConnectOptions=$Using:ConnectOptions + If (Connect-ControlAPI @ConnectOptions -SkipCheck -Quiet) { + $ServiceRestartAttempt = Invoke-ControlCommand -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('$($Using:LTPoShURI)') | iex; Get-LTServiceInfo" -TimeOut 60000 -MaxLength 10240 + return $ServiceRestartAttempt + } } | out-null } ElseIf ($Action -eq 'Update') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - Update Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; Update-LTService" -TimeOut 120000 -MaxLength 10240 - return $ServiceRestartAttempt + $ConnectOptions=$Using:ConnectOptions + If (Connect-ControlAPI @ConnectOptions -SkipCheck -Quiet) { + $ServiceRestartAttempt = Invoke-ControlCommand -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('$($Using:LTPoShURI)') | iex; Update-LTService" -TimeOut 60000 -MaxLength 10240 + return $ServiceRestartAttempt + } } | out-null } ElseIf ($Action -eq 'Restart') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - Restart Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; Restart-LTService" -TimeOut 120000 -MaxLength 10240 - return $ServiceRestartAttempt + $ConnectOptions=$Using:ConnectOptions + If (Connect-ControlAPI @ConnectOptions -SkipCheck -Quiet) { + $ServiceRestartAttempt = Invoke-ControlCommand -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('$($Using:LTPoShURI)') | iex; Restart-LTService" -TimeOut 60000 -MaxLength 10240 + return $ServiceRestartAttempt + } } | out-null } ElseIf ($Action -eq 'Reinstall') { $ObjectCapture | Start-RSJob -Throttle $BatchSize -Name {"$($_.ComputerName) - $($_.ComputerID) - ReInstall Service"} -ScriptBlock { Import-Module AutomateAPI -Force - $ServiceRestartAttempt = Invoke-ControlCommand -Server $using:ControlServer -Credential $using:ControlAPICredentials -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString($Using:LTPoShURI) | iex; ReInstall-LTService" -TimeOut 360000 -MaxLength 10240 - return $ServiceRestartAttempt + $ConnectOptions=$Using:ConnectOptions + If (Connect-ControlAPI @ConnectOptions -SkipCheck -Quiet) { + $ServiceRestartAttempt = Invoke-ControlCommand -SessionID $($_.SessionID) -Powershell -Command "(new-object Net.WebClient).DownloadString('$($Using:LTPoShURI)') | iex; ReInstall-LTService" -TimeOut 60000 -MaxLength 10240 + return $ServiceRestartAttempt + } } | out-null } Else { Write-Host -BackgroundColor Yellow -ForegroundColor Red "Action $Action is not currently supported." From b30deaad14f4ee9e2d5ced7b8f486a685a277d3b Mon Sep 17 00:00:00 2001 From: Darren White Date: Wed, 20 Feb 2019 10:44:38 -0800 Subject: [PATCH 19/21] Formatting and Optimizations --- Public/Compare-AutomateControlStatus.ps1 | 49 ++++++++---------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/Public/Compare-AutomateControlStatus.ps1 b/Public/Compare-AutomateControlStatus.ps1 index bf67247..2c5dec7 100644 --- a/Public/Compare-AutomateControlStatus.ps1 +++ b/Public/Compare-AutomateControlStatus.ps1 @@ -11,43 +11,37 @@ function Compare-AutomateControlStatus { [switch]$Quiet ) - begin { + Begin { $ComputerArray = @() $ObjectRebuild = @() $ReturnedObject = @() } - process { - if ($ComputerObject) { + Process { + If ($ComputerObject) { $ObjectRebuild += $ComputerObject } - } - end { + End { # The primary concern now is to get out the ComputerIDs of the machines of the objects # We want to support all ComputerIDs being called if no computer object is passed in - If(!$Quiet){Write-Host -BackgroundColor Blue -ForegroundColor White "Checking to see if the recommended Internal Monitor is present"} + If (!$Quiet){Write-Host -BackgroundColor Blue -ForegroundColor White "Checking to see if the recommended Internal Monitor is present"} $AutoControlSessions=@{}; - $InternalMonitorMethod = $false $Null=Get-AutomateAPIGeneric -Endpoint "InternalMonitorResults" -allresults -condition "(Name like '%GetControlSessionIDs%')" -EA 0 | Where-Object {($_.computerid -and $_.computerid -gt 0 -and $_.IdentityField -and $_.IdentityField -match '.+')} | ForEach-Object {$AutoControlSessions.Add($_.computerid,$_.IdentityField)}; - # Check to see if the Internal Monitor method has results - if ($AutoControlSessions.Count -gt 0){$InternalMonitorMethod = $true; If(!$Quiet){Write-Host -BackgroundColor Green -ForegroundColor Black "Internal monitor found. Processing results."} } Else {If(!$Quiet){Write-Host -ForegroundColor Black -BackgroundColor Yellow "Internal monitor not found. This cmdlet is significantly faster with it. See https://www.github.com/gavsto/automateapi"}} - # Check to see if any Computers were specified in the incoming object - if(!$ObjectRebuild.Count -gt 0){$FullLookupMethod = $true} + If (!$ObjectRebuild.Count -gt 0){$FullLookupMethod = $true} - if ($FullLookupMethod) { + If ($FullLookupMethod) { $ObjectRebuild = Get-AutomateComputer -AllComputers | Select-Object Id, ComputerName, @{Name = 'ClientName'; Expression = {$_.Client.Name}}, OperatingSystemName, Status } - foreach ($computer in $ObjectRebuild) { - If(!$InternalMonitorMethod) + Foreach ($computer in $ObjectRebuild) { + If (!$AutoControlSessions[[int]$Computer.ID]) { $AutomateControlGUID = Get-AutomateControlInfo -ComputerID $($computer | Select-Object -ExpandProperty id) | Select-Object -ExpandProperty SessionID - } - else { + } Else { $AutomateControlGUID = $AutoControlSessions[[int]$Computer.ID] } @@ -71,23 +65,18 @@ function Compare-AutomateControlStatus { #Control Sessions $ControlSessions = Get-ControlSessions - foreach ($final in $ComputerArray) { + Foreach ($final in $ComputerArray) { - if (![string]::IsNullOrEmpty($Final.SessionID)) { - if ($ControlSessions.Containskey($Final.SessionID)) { + If (![string]::IsNullOrEmpty($Final.SessionID)) { + If ($ControlSessions.Containskey($Final.SessionID)) { $ResultControlSessionStatus = $ControlSessions[$Final.SessionID] - } - else - { + } Else { $ResultControlSessionStatus = "GUID Not in Control or No Connection Events" } - } - else - { + } Else { $ResultControlSessionStatus = "Control not installed or GUID not in Automate" } - $CAReturn = "" $CAReturn = [pscustomobject] @{ ComputerID = $final.ComputerID @@ -102,14 +91,10 @@ function Compare-AutomateControlStatus { $ReturnedObject += $CAReturn } - if ($AllResults) { + If ($AllResults) { $ReturnedObject - } - else - { + } Else { $ReturnedObject | Where-Object{($_.OnlineStatusControl -eq $true) -and ($_.OnlineStatusAutomate -eq 'Offline') } } - - } } \ No newline at end of file From c45dbb4e74d20895c5207c12540febc326d20d72 Mon Sep 17 00:00:00 2001 From: Gavin Stone Date: Thu, 21 Feb 2019 00:23:39 +0000 Subject: [PATCH 20/21] Documentation updates + small fixes. Possible release candidate --- AutomateAPI.psd1 | Bin 7940 -> 7940 bytes Docs/Compare-AutomateControlStatus.md | 49 +++++++++++--------- Docs/Connect-AutomateAPI.md | 37 +++++++++++---- Docs/Connect-ControlAPI.md | 53 ++++++++++++++++++--- Docs/Get-AutomateComputer.md | 56 +++++++++++++++++++++-- Docs/Get-AutomateControlInfo.md | 4 +- Docs/Get-CredentialsLocallyStored.md | 24 +++++++++- Docs/Invoke-ControlCommand.md | 51 ++++++++++----------- Docs/Repair-AutomateAgent.md | 35 ++++++++++---- Docs/Set-CredentialsLocallyStored.md | 38 +++++++-------- Private/Get-CWAIKToken.ps1 | 2 + Public/Compare-AutomateControlStatus.ps1 | 33 ++++++++++++- Public/Connect-AutomateAPI.ps1 | 4 +- Public/Connect-ControlAPI.ps1 | 4 +- Public/Get-AutomateControlInfo.ps1 | 4 ++ Public/Invoke-ControlCommand.ps1 | 6 ++- Public/Repair-AutomateAgent.ps1 | 12 +++-- 17 files changed, 306 insertions(+), 106 deletions(-) diff --git a/AutomateAPI.psd1 b/AutomateAPI.psd1 index 660474126ab90109728f10f160c8e082c07dd56e..1a471ff83fecd0b2c4278cc5aa82ed43fa1a2c70 100644 GIT binary patch delta 14 VcmZp%Yq8s4!o+C4*_7$L3;-i;1b6@d delta 14 VcmZp%Yq8s4!o+B{*_7$L3;-i&1a|-c diff --git a/Docs/Compare-AutomateControlStatus.md b/Docs/Compare-AutomateControlStatus.md index f5c251d..5017f0a 100644 --- a/Docs/Compare-AutomateControlStatus.md +++ b/Docs/Compare-AutomateControlStatus.md @@ -8,7 +8,7 @@ schema: 2.0.0 # Compare-AutomateControlStatus ## SYNOPSIS -{{Fill in the Synopsis}} +Compares Automate Online Status with Control, and outputs all machines online in Control and not in Automate ## SYNTAX @@ -17,51 +17,54 @@ Compare-AutomateControlStatus [[-ComputerObject] ] [-AllResults] [-Quiet ``` ## DESCRIPTION -{{Fill in the Description}} +Compares Automate Online Status with Control, and outputs all machines online in Control and not in Automate ## EXAMPLES -### Example 1 -```powershell -PS C:\> {{ Add example code here }} +### EXAMPLE 1 +``` +Get-AutomateComputer -ComputerID 5 | Compare-AutomateControlStatus ``` -{{ Add example description here }} +### EXAMPLE 2 +``` +Get-AutomateComputer -Online $False | Compare-AutomateControlStatus +``` ## PARAMETERS -### -AllResults -{{Fill AllResults Description}} +### -ComputerObject +Can be taken from the pipeline in the form of Get-AutomateComputer -ComputerID 5 | Compare-AutomateControlStatus ```yaml -Type: SwitchParameter +Type: Object Parameter Sets: (All) Aliases: Required: False -Position: Named +Position: 1 Default value: None -Accept pipeline input: False +Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` -### -ComputerObject -{{Fill ComputerObject Description}} +### -AllResults +Instead of outputting a comparison it outputs everything, which include two columns indicating online status ```yaml -Type: Object +Type: SwitchParameter Parameter Sets: (All) Aliases: Required: False -Position: 0 -Default value: None -Accept pipeline input: True (ByValue) +Position: Named +Default value: False +Accept pipeline input: False Accept wildcard characters: False ``` ### -Quiet -{{Fill Quiet Description}} +Doesn't output any log messages ```yaml Type: SwitchParameter @@ -70,7 +73,7 @@ Aliases: Required: False Position: Named -Default value: None +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` @@ -81,11 +84,13 @@ For more information, see about_CommonParameters (http://go.microsoft.com/fwlink ## INPUTS -### System.Object - ## OUTPUTS -### System.Object +### An object containing Online status for Control and Automate ## NOTES +Version: 1.1 +Author: Gavin Stone +Creation Date: 20/01/2019 +Purpose/Change: Initial script development ## RELATED LINKS diff --git a/Docs/Connect-AutomateAPI.md b/Docs/Connect-AutomateAPI.md index 7b2eb60..d64ab40 100644 --- a/Docs/Connect-AutomateAPI.md +++ b/Docs/Connect-AutomateAPI.md @@ -20,10 +20,15 @@ Connect-AutomateAPI [-Server ] [-AuthorizationToken ] [-SkipChec ### credential ``` -Connect-AutomateAPI [-Credentials ] [-Server ] [-TwoFactorToken ] +Connect-AutomateAPI [-Credential ] [-Server ] [-SkipCheck] [-TwoFactorToken ] [-Force] [-Quiet] [] ``` +### verify +``` +Connect-AutomateAPI [-Server ] [-AuthorizationToken ] [-Verify] [-Quiet] [] +``` + ## DESCRIPTION Connects to the Automate API and returns a bearer token which when passed with each requests grants up to an hours worth of access. @@ -41,8 +46,8 @@ Connect-AutomateAPI -Quiet ## PARAMETERS -### -Credentials -Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass +### -Credential +{{Fill Credential Description}} ```yaml Type: PSCredential @@ -73,26 +78,42 @@ Accept wildcard characters: False ``` ### -AuthorizationToken -{{Fill AuthorizationToken Description}} +Used internally when quietly refreshing the Token ```yaml Type: String -Parameter Sets: refresh +Parameter Sets: refresh, verify Aliases: Required: False Position: Named -Default value: ($Script:CWACredentials.Authorization -replace 'Bearer ','') +Default value: ($Script:CWAToken.Authorization -replace 'Bearer ','') Accept pipeline input: False Accept wildcard characters: False ``` ### -SkipCheck -{{Fill SkipCheck Description}} +Used internally when quietly refreshing the Token + +```yaml +Type: SwitchParameter +Parameter Sets: refresh, credential +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Verify +Specifies to test the current token, and if it is not valid attempt to obtain a new one using the current credentials. +Does not refresh (re-issue) the current token. ```yaml Type: SwitchParameter -Parameter Sets: refresh +Parameter Sets: verify Aliases: Required: False diff --git a/Docs/Connect-ControlAPI.md b/Docs/Connect-ControlAPI.md index b64adca..ccc1760 100644 --- a/Docs/Connect-ControlAPI.md +++ b/Docs/Connect-ControlAPI.md @@ -17,12 +17,22 @@ Adds credentials required to connect to the Control API Connect-ControlAPI [-Server ] [-Quiet] [] ``` +### verify +``` +Connect-ControlAPI [-Credential ] [-Server ] [-Verify] [-Quiet] [] +``` + ### credential ``` -Connect-ControlAPI [-ControlCredentials ] [-Server ] [-Force] [-SkipCheck] [-Quiet] +Connect-ControlAPI [-Credential ] [-Server ] [-Force] [-SkipCheck] [-Quiet] [] ``` +### apikey +``` +Connect-ControlAPI [-Server ] [-APIKey ] [-SkipCheck] [-Quiet] [] +``` + ## DESCRIPTION Creates a Control hashtable in memory containing the server and username/password so that it can be used in other functions that connect to ConnectWise Control. Unfortunately the Control API does not support 2FA. @@ -36,16 +46,16 @@ All values will be prompted for one by one: Connect-ControlAPI All values needed to Automatically create appropriate output -Connect-ControlAPI -Server "https://control.rancorthebeast.com:8040" -ControlCredentials $CredentialsToPass +Connect-ControlAPI -Server "https://control.rancorthebeast.com:8040" -Credentials $CredentialsToPass ## PARAMETERS -### -ControlCredentials +### -Credential Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass ```yaml Type: PSCredential -Parameter Sets: credential +Parameter Sets: verify, credential Aliases: Required: False @@ -71,6 +81,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -APIKey +Automate APIKey for Control Extension + +```yaml +Type: Object +Parameter Sets: apikey +Aliases: + +Required: False +Position: Named +Default value: ([SecureString]$Script:ControlAPIKey) +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Force \[Parameter(ParameterSetName = 'credential', Mandatory = $False)\] \[String\]$TwoFactorToken, @@ -87,12 +112,28 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Verify +Attempt to verify Cached API key or Credentials. +Invalid results will be removed. + +```yaml +Type: SwitchParameter +Parameter Sets: verify +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -SkipCheck -{{Fill SkipCheck Description}} +Used internally when quietly refreshing the Token ```yaml Type: SwitchParameter -Parameter Sets: credential +Parameter Sets: credential, apikey Aliases: Required: False diff --git a/Docs/Get-AutomateComputer.md b/Docs/Get-AutomateComputer.md index f63bcad..29cac6b 100644 --- a/Docs/Get-AutomateComputer.md +++ b/Docs/Get-AutomateComputer.md @@ -19,17 +19,20 @@ Get-AutomateComputer [[-ComputerID] ] [] ### AllResults ``` -Get-AutomateComputer [-AllComputers] [] +Get-AutomateComputer [-AllComputers] [-IncludeFields ] [-ExcludeFields ] [-OrderBy ] + [] ``` ### ByCondition ``` -Get-AutomateComputer [[-Condition] ] [] +Get-AutomateComputer [-Condition ] [-IncludeFields ] [-ExcludeFields ] + [-OrderBy ] [] ``` ### CustomBuiltCondition ``` -Get-AutomateComputer [-ClientName ] [-ClientId ] [-LocationId ] [-LocationName ] +Get-AutomateComputer [-IncludeFields ] [-ExcludeFields ] [-OrderBy ] + [-ClientName ] [-ClientId ] [-LocationId ] [-LocationName ] [-ComputerName ] [-OpenPort ] [-OperatingSystem ] [-DomainName ] [-NotSeenInDays ] [-Comment ] [-LastWindowsUpdateInDays ] [-AntiVirusDefinitionInDays ] [-LocalIPAddress ] [-GatewayIPAddress ] @@ -114,7 +117,52 @@ Parameter Sets: ByCondition Aliases: Required: False -Position: 1 +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -IncludeFields +A comma separated list of fields that you want including in the returned computer object. + +```yaml +Type: String +Parameter Sets: AllResults, ByCondition, CustomBuiltCondition +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExcludeFields +A comma separated list of fields that you want excluding in the returned computer object. + +```yaml +Type: String +Parameter Sets: AllResults, ByCondition, CustomBuiltCondition +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -OrderBy +A comma separated list of fields that you want to order by finishing with either an asc or desc. + +```yaml +Type: String +Parameter Sets: AllResults, ByCondition, CustomBuiltCondition +Aliases: + +Required: False +Position: Named Default value: None Accept pipeline input: False Accept wildcard characters: False diff --git a/Docs/Get-AutomateControlInfo.md b/Docs/Get-AutomateControlInfo.md index 143888a..e70b048 100644 --- a/Docs/Get-AutomateControlInfo.md +++ b/Docs/Get-AutomateControlInfo.md @@ -50,7 +50,7 @@ Accept wildcard characters: False ``` ### -ID -{{Fill ID Description}} +Taken from the Pipeline, IE Get-AutomateComputer -ComputerID 5 | Get-AutomateControlInfo ```yaml Type: Int16[] @@ -65,7 +65,7 @@ Accept wildcard characters: False ``` ### -ComputerObjects -{{Fill ComputerObjects Description}} +Used for Pipeline input from Get-AutomateComputer ```yaml Type: Object diff --git a/Docs/Get-CredentialsLocallyStored.md b/Docs/Get-CredentialsLocallyStored.md index 0d8cd6c..dcba6c8 100644 --- a/Docs/Get-CredentialsLocallyStored.md +++ b/Docs/Get-CredentialsLocallyStored.md @@ -22,9 +22,14 @@ Get-CredentialsLocallyStored [-Automate] [-CredentialDirectory ] [] [] ``` +### All +``` +Get-CredentialsLocallyStored [-All] [] +``` + ### Custom ``` -Get-CredentialsLocallyStored [-CredentialPath ] [] +Get-CredentialsLocallyStored -CredentialPath [] ``` ## DESCRIPTION @@ -41,6 +46,21 @@ PS C:\> {{ Add example code here }} ## PARAMETERS +### -All +{{Fill All Description}} + +```yaml +Type: SwitchParameter +Parameter Sets: All +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Automate {{Fill Automate Description}} @@ -94,7 +114,7 @@ Type: String Parameter Sets: Custom Aliases: -Required: False +Required: True Position: Named Default value: None Accept pipeline input: False diff --git a/Docs/Invoke-ControlCommand.md b/Docs/Invoke-ControlCommand.md index df311fa..303ef43 100644 --- a/Docs/Invoke-ControlCommand.md +++ b/Docs/Invoke-ControlCommand.md @@ -13,8 +13,8 @@ Will issue a command against a given machine and return the results. ## SYNTAX ``` -Invoke-ControlCommand [[-Server] ] [[-Credentials] ] [-GUID] [[-Command] ] - [[-TimeOut] ] [-PowerShell] [[-Group] ] [[-MaxLength] ] [] +Invoke-ControlCommand [[-Server] ] [[-Credentials] ] [-SessionID] + [[-Command] ] [[-TimeOut] ] [-PowerShell] [[-MaxLength] ] [] ``` ## DESCRIPTION @@ -24,14 +24,19 @@ Will issue a command against a given machine and return the results. ### EXAMPLE 1 ``` -Invoke-ControlCommand -GUID $GUID -Command 'hostname' +Get-AutomateComputer -ComputerID 5 | Get-ControlSessions | Invoke-ControlCommand -Powershell -Command "Get-Service" +``` + +### EXAMPLE 2 +``` +Invoke-ControlCommand -SessionID $SessionID -Command 'hostname' ``` Will return the hostname of the machine. -### EXAMPLE 2 +### EXAMPLE 3 ``` -Invoke-ControlCommand -GUID $GUID -User $User -Password $Password -TimeOut 120000 -Command 'iwr -UseBasicParsing "https://bit.ly/ltposh" | iex; Restart-LTService' -PowerShell +Invoke-ControlCommand -SessionID $SessionID -User $User -Password $Password -TimeOut 120000 -Command 'iwr -UseBasicParsing "https://bit.ly/ltposh" | iex; Restart-LTService' -PowerShell ``` Will restart the Automate agent on the target machine. @@ -68,19 +73,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -GUID +### -SessionID The GUID identifier for the machine you wish to connect to. -You can retrieve session info with the 'Get-CWCSessions' commandlet +You can retrieve session info with the 'Get-ControlSessions' commandlet +SessionIDs can be provided via the pipeline. +IE - Get-AutomateComputer -ComputerID 5 | Get-ControlSessions | Invoke-ControlCommand -Powershell -Command "Get-Service" ```yaml -Type: Guid +Type: Guid[] Parameter Sets: (All) Aliases: Required: True Position: 3 Default value: None -Accept pipeline input: False +Accept pipeline input: True (ByPropertyName, ByValue) Accept wildcard characters: False ``` @@ -130,21 +137,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -Group -Name of session group to use. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 6 -Default value: All Machines -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -MaxLength {{Fill MaxLength Description}} @@ -154,7 +146,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 7 +Position: 6 Default value: 5000 Accept pipeline input: False Accept wildcard characters: False @@ -170,10 +162,17 @@ For more information, see about_CommonParameters (http://go.microsoft.com/fwlink ### The output of the Command provided. ## NOTES -Version: 1.0 +Version: 2.0 Author: Chris Taylor Modified By: Gavin Stone +Modified By: Darren White Creation Date: 1/20/2016 Purpose/Change: Initial script development +Update Date: 2019-02-19 +Author: Darren White +Purpose/Change: Enable Pipeline support. +Enable processing using Automate Control Extension. +The cached APIKey will be used if present. + ## RELATED LINKS diff --git a/Docs/Repair-AutomateAgent.md b/Docs/Repair-AutomateAgent.md index 84dca3e..3391bf9 100644 --- a/Docs/Repair-AutomateAgent.md +++ b/Docs/Repair-AutomateAgent.md @@ -13,8 +13,8 @@ Takes changed detected in Compare-AutomateControlStatus and performs a specified ## SYNTAX ``` -Repair-AutomateAgent [[-Action] ] [[-BatchSize] ] [[-AutomateControlStatusObject] ] - [-WhatIf] [-Confirm] [] +Repair-AutomateAgent [[-Action] ] [[-BatchSize] ] [[-LTPoShURI] ] + [[-AutomateControlStatusObject] ] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION @@ -24,18 +24,18 @@ Takes changed detected in Compare-AutomateControlStatus and performs a specified ### EXAMPLE 1 ``` -Get-AutomateComputer -Online $False | Compare-AutomateControlStatus | Repair-AutomateAgent -Check Restart +Get-AutomateComputer -Online $False | Compare-AutomateControlStatus | Repair-AutomateAgent -Action Check ``` ### EXAMPLE 2 ``` -Get-AutomateComputer -Online $False | Compare-AutomateControlStatus | Repair-AutomateAgent -Check Reinstall +Get-AutomateComputer -Online $False | Compare-AutomateControlStatus | Repair-AutomateAgent -Action Restart ``` ## PARAMETERS ### -Action -{{Fill Action Description}} +Takes either Update, Restart, Reinstall or Check ```yaml Type: String @@ -50,7 +50,9 @@ Accept wildcard characters: False ``` ### -BatchSize -{{Fill BatchSize Description}} +When multiple jobs are run, they run in Parallel. +Batch size determines how many jobs can run at once. +Default is 10 ```yaml Type: Int32 @@ -59,13 +61,28 @@ Aliases: Required: False Position: 2 -Default value: 5 +Default value: 10 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LTPoShURI +If you do not wish to use the LT Posh module on GitHub you can use your own link to the LTPosh Module with this parameter + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: $Script:LTPoShURI Accept pipeline input: False Accept wildcard characters: False ``` ### -AutomateControlStatusObject -{{Fill AutomateControlStatusObject Description}} +Object taken from the Pipeline from Compare-AutomateControlStatus ```yaml Type: Object @@ -73,7 +90,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 3 +Position: 4 Default value: None Accept pipeline input: True (ByValue) Accept wildcard characters: False diff --git a/Docs/Set-CredentialsLocallyStored.md b/Docs/Set-CredentialsLocallyStored.md index 088fba9..37ac9d2 100644 --- a/Docs/Set-CredentialsLocallyStored.md +++ b/Docs/Set-CredentialsLocallyStored.md @@ -14,17 +14,17 @@ Sets credential objects on a server that has never had them before ### Automate ``` -Set-CredentialsLocallyStored [-Automate] [-CredentialDirectory ] [] +Set-CredentialsLocallyStored [-Automate] [-Save] [-CredentialDirectory ] [] ``` ### All ``` -Set-CredentialsLocallyStored [-All] [] +Set-CredentialsLocallyStored [-All] [-Save] [] ``` ### Control ``` -Set-CredentialsLocallyStored [-Control] [-CredentialDirectory ] [] +Set-CredentialsLocallyStored [-Control] [-Save] [-CredentialDirectory ] [] ``` ### Custom @@ -45,22 +45,7 @@ Set-CredentialsLocallyStored -Automate ### EXAMPLE 2 ``` -Set-CredentialsLocallyStored -ITGlue -``` - -### EXAMPLE 3 -``` -Set-CredentialsLocallyStored -MySQL -``` - -### EXAMPLE 4 -``` -Set-CredentialsLocallyStored -Office365 -``` - -### EXAMPLE 5 -``` -Set-CredentialsLocallyStored -Custom -CredentialPath "C:\Credentials\Custom Credentials.txt" +Set-CredentialsLocallyStored -Custom -CredentialDisplayName 'Office365' -CredentialDirectory "C:\Credentials" ``` ## PARAMETERS @@ -140,6 +125,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Save +{{Fill Save Description}} + +```yaml +Type: SwitchParameter +Parameter Sets: Automate, All, Control +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -CredentialDirectory {{Fill CredentialDirectory Description}} diff --git a/Private/Get-CWAIKToken.ps1 b/Private/Get-CWAIKToken.ps1 index 49588a8..8bcf813 100644 --- a/Private/Get-CWAIKToken.ps1 +++ b/Private/Get-CWAIKToken.ps1 @@ -13,6 +13,8 @@ function Get-CWAIKToken { $APIKey = $([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($APIKey))) } + # If you bothered to actually inspect this module thoroughly, come PM @Gavsto in Slack and win a free Gavsto Karma Point ;) + $TimeStepSeconds = 600 $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 $epochsteps = [long]$((New-TimeSpan -Start $origin -End $(get-date).ToUniversalTime()).TotalSeconds/$TimeStepSeconds) diff --git a/Public/Compare-AutomateControlStatus.ps1 b/Public/Compare-AutomateControlStatus.ps1 index 2c5dec7..e54c143 100644 --- a/Public/Compare-AutomateControlStatus.ps1 +++ b/Public/Compare-AutomateControlStatus.ps1 @@ -1,4 +1,27 @@ function Compare-AutomateControlStatus { + <# + .SYNOPSIS + Compares Automate Online Status with Control, and outputs all machines online in Control and not in Automate + .DESCRIPTION + Compares Automate Online Status with Control, and outputs all machines online in Control and not in Automate + .PARAMETER ComputerObject + Can be taken from the pipeline in the form of Get-AutomateComputer -ComputerID 5 | Compare-AutomateControlStatus + .PARAMETER AllResults + Instead of outputting a comparison it outputs everything, which include two columns indicating online status + .PARAMETER Quiet + Doesn't output any log messages + .OUTPUTS + An object containing Online status for Control and Automate + .NOTES + Version: 1.1 + Author: Gavin Stone + Creation Date: 20/01/2019 + Purpose/Change: Initial script development + .EXAMPLE + Get-AutomateComputer -ComputerID 5 | Compare-AutomateControlStatus + .EXAMPLE + Get-AutomateComputer -Online $False | Compare-AutomateControlStatus + #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] @@ -45,11 +68,19 @@ function Compare-AutomateControlStatus { $AutomateControlGUID = $AutoControlSessions[[int]$Computer.ID] } + If([string]::IsNullOrEmpty($Computer.Client.Name)) + { + $FiClientName = $Computer.ClientName + } + else { + $FiClientName = $Computer.Client.Name + } + $FinalComputerObject = "" $FinalComputerObject = [pscustomobject] @{ ComputerID = $Computer.ID ComputerName = $Computer.ComputerName - ClientName = $Computer.Client.Name + ClientName = $FiClientName OperatingSystemName = $Computer.OperatingSystemName OnlineStatusAutomate = $Computer.Status OnlineStatusControl = '' diff --git a/Public/Connect-AutomateAPI.ps1 b/Public/Connect-AutomateAPI.ps1 index 8cee38a..8b7d5b7 100644 --- a/Public/Connect-AutomateAPI.ps1 +++ b/Public/Connect-AutomateAPI.ps1 @@ -10,7 +10,9 @@ The address to your Automate Server. Example 'rancor.hostedrmm.com' Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass .PARAMETER TwoFactorToken Takes a string that represents the 2FA number -.PARAMETER Token +.PARAMETER AuthorizationToken +Used internally when quietly refreshing the Token +.PARAMETER SkipCheck Used internally when quietly refreshing the Token .PARAMETER Verify Specifies to test the current token, and if it is not valid attempt to obtain a new one using the current credentials. Does not refresh (re-issue) the current token. diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index 900d21a..7ca4493 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -6,7 +6,7 @@ function Connect-ControlAPI { Creates a Control hashtable in memory containing the server and username/password so that it can be used in other functions that connect to ConnectWise Control. Unfortunately the Control API does not support 2FA. .PARAMETER Server The address to your Control Server. Example 'https://control.rancorthebeast.com:8040' - .PARAMETER Credentials + .PARAMETER Credential Takes a standard powershell credential object, this can be built with $CredentialsToPass = Get-Credential, then pass $CredentialsToPass .PARAMETER APIKey Automate APIKey for Control Extension @@ -14,6 +14,8 @@ function Connect-ControlAPI { Attempt to verify Cached API key or Credentials. Invalid results will be removed. .PARAMETER Quiet Will not output any standard logging messages + .PARAMETER SkipCheck + Used internally when quietly refreshing the Token .OUTPUTS Two script variables with server and credentials. Returns True or False .NOTES diff --git a/Public/Get-AutomateControlInfo.ps1 b/Public/Get-AutomateControlInfo.ps1 index 2ff783a..99151e2 100644 --- a/Public/Get-AutomateControlInfo.ps1 +++ b/Public/Get-AutomateControlInfo.ps1 @@ -6,6 +6,10 @@ Retrieve data from Automate API Control Extension Connects to the Automate API Control Extension and returns an object with Control Session data .PARAMETER ComputerID The Automate ComputerID to retrieve information on +.PARAMETER ID +Taken from the Pipeline, IE Get-AutomateComputer -ComputerID 5 | Get-AutomateControlInfo +.PARAMETER ComputerObjects +Used for Pipeline input from Get-AutomateComputer .OUTPUTS Custom object with the ComputerID and Control SessionID. Additional properties from the return data will be included. .NOTES diff --git a/Public/Invoke-ControlCommand.ps1 b/Public/Invoke-ControlCommand.ps1 index 7b48272..3335af9 100644 --- a/Public/Invoke-ControlCommand.ps1 +++ b/Public/Invoke-ControlCommand.ps1 @@ -6,8 +6,9 @@ function Invoke-ControlCommand { Will issue a command against a given machine and return the results. .PARAMETER SessionID The GUID identifier for the machine you wish to connect to. - You can retrieve session info with the 'Get-CWCSessions' commandlet + You can retrieve session info with the 'Get-ControlSessions' commandlet SessionIDs can be provided via the pipeline. + IE - Get-AutomateComputer -ComputerID 5 | Get-ControlSessions | Invoke-ControlCommand -Powershell -Command "Get-Service" .PARAMETER Command The command you wish to issue to the machine. .PARAMETER TimeOut @@ -29,7 +30,8 @@ function Invoke-ControlCommand { Update Date: 2019-02-19 Author: Darren White Purpose/Change: Enable Pipeline support. Enable processing using Automate Control Extension. The cached APIKey will be used if present. - + .EXAMPLE + Get-AutomateComputer -ComputerID 5 | Get-ControlSessions | Invoke-ControlCommand -Powershell -Command "Get-Service" .EXAMPLE Invoke-ControlCommand -SessionID $SessionID -Command 'hostname' Will return the hostname of the machine. diff --git a/Public/Repair-AutomateAgent.ps1 b/Public/Repair-AutomateAgent.ps1 index 26e1f8a..b200bb2 100644 --- a/Public/Repair-AutomateAgent.ps1 +++ b/Public/Repair-AutomateAgent.ps1 @@ -4,8 +4,14 @@ function Repair-AutomateAgent { Takes changed detected in Compare-AutomateControlStatus and performs a specified repair on them .DESCRIPTION Takes changed detected in Compare-AutomateControlStatus and performs a specified repair on them -.PARAMETER Check - Triggers a different type of check depending on what is passed either Update, Restart, Reinstall or Check +.PARAMETER Action + Takes either Update, Restart, Reinstall or Check +.PARAMETER BatchSize + When multiple jobs are run, they run in Parallel. Batch size determines how many jobs can run at once. Default is 10 +.PARAMETER LTPoShURI + If you do not wish to use the LT Posh module on GitHub you can use your own link to the LTPosh Module with this parameter +.PARAMETER AutomateControlStatusObject + Object taken from the Pipeline from Compare-AutomateControlStatus .EXAMPLE Get-AutomateComputer -Online $False | Compare-AutomateControlStatus | Repair-AutomateAgent -Action Check .EXAMPLE @@ -25,7 +31,7 @@ function Repair-AutomateAgent { [Parameter(Mandatory = $False)] [ValidateRange(1,50)] [int] - $BatchSize = 5, + $BatchSize = 10, [Parameter(Mandatory = $False)] [String]$LTPoShURI = $Script:LTPoShURI, From 79b1a4cc4efb0bb136ed3b2d1796989a2e285298 Mon Sep 17 00:00:00 2001 From: Darren White Date: Wed, 20 Feb 2019 16:40:17 -0800 Subject: [PATCH 21/21] Avoid prompting with -Verify --- Public/Connect-ControlAPI.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Public/Connect-ControlAPI.ps1 b/Public/Connect-ControlAPI.ps1 index 900d21a..78d8966 100644 --- a/Public/Connect-ControlAPI.ps1 +++ b/Public/Connect-ControlAPI.ps1 @@ -68,7 +68,7 @@ function Connect-ControlAPI { # if ($TwoFactorToken -match '.+') {$Force=$True} $TwoFactorNeeded=$False - If (!$Quiet) { + If (!$Quiet -and !$Verify) { While (!($Server -match '.+')) { $Server = Read-Host -Prompt "Please enter your Control Server address, the full URL. IE https://control.rancorthebeast.com:8040" } @@ -117,7 +117,7 @@ function Connect-ControlAPI { Do { $ControlAPITestURI = ($Server + '/Services/PageService.ashx/GetHostSessionInfo') If (!$Quiet) { - If (!$Credential) { + If (!$Credential -and !$Verify) { If (!$testCredentials -or $Force) { Write-Debug "No Credentials were provided and no existing Token was found, or -Force was specified" $testCredentials = $Null @@ -160,7 +160,7 @@ function Connect-ControlAPI { Write-Debug "Request Results: $($ControlAPITokenResult|ConvertTo-Json -Depth 5 -Compress)" $AuthorizationResult=$ControlAPITokenResult.ProductVersion $TwoFactorNeeded=$ControlAPITokenResult.IsTwoFactorRequired - } Until ($Quiet -or ![string]::IsNullOrEmpty($AuthorizationResult) -or + } Until ($Quiet -or $Verify -or ![string]::IsNullOrEmpty($AuthorizationResult) -or ($TwoFactorNeeded -ne $True -and $Credential) -or ($TwoFactorNeeded -eq $True -and $TwoFactorToken -ne '') )