Private/Invoke-EFManagementAgentOperation.ps1
|
function Get-EFManagementAgentServiceController { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$ServiceName ) Microsoft.PowerShell.Management\Get-Service -Name $ServiceName -ErrorAction Stop } function Invoke-EFManagementAgentOperation { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Intune', 'ConfigurationManager')] [string]$Agent, [Parameter(Mandatory)] [ValidateSet('StartAgentService', 'RestartAgentService', 'RequestMachinePolicy', 'EvaluateMachinePolicy')] [string]$Action, [ValidateRange(5, 300)] [int]$TimeoutSeconds = 60 ) $definition = Get-EFManagementAgentDefinition -Agent $Agent if ($Action -in @('StartAgentService', 'RestartAgentService')) { $controller = $null $operationTimer = [Diagnostics.Stopwatch]::StartNew() $beforeServiceStatus = $null $afterServiceStatus = $null $changeMayHaveOccurred = $false $getRemainingTimeout = { $remainingMilliseconds = ($TimeoutSeconds * 1000.0) - $operationTimer.Elapsed.TotalMilliseconds if ($remainingMilliseconds -le 0) { throw [TimeoutException]::new( "The local management agent service action exceeded its $TimeoutSeconds second overall timeout." ) } [TimeSpan]::FromMilliseconds([math]::Max(1, [math]::Floor($remainingMilliseconds))) } try { $controller = Get-EFManagementAgentServiceController -ServiceName ([string]$definition.ServiceName) $controller.Refresh() $beforeServiceStatus = $controller.Status.ToString() if ($Action -eq 'StartAgentService' -and $beforeServiceStatus -eq 'Running') { return [pscustomobject][ordered]@{ Outcome = 'NotRequired' IsSuccessful = $true ChangeMayHaveOccurred = $false RequestAccepted = $false ImmediateEffectVerified = $true BeforeServiceStatus = $beforeServiceStatus AfterServiceStatus = $beforeServiceStatus Message = 'The local management agent service is already running. No change was needed.' NextStep = 'No service action is needed. Use the local health result and your organization management console for any further investigation.' RecoveryGuidance = 'No change was made.' } } if ($Action -eq 'RestartAgentService' -and $beforeServiceStatus -in @('Running', 'StartPending', 'ContinuePending', 'PausePending', 'Paused')) { $hasRunningDependent = $false foreach ($dependentService in @($controller.DependentServices)) { if ($dependentService.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Stopped) { $hasRunningDependent = $true break } } if ($hasRunningDependent) { return [pscustomobject][ordered]@{ Outcome = 'Failed' IsSuccessful = $false ChangeMayHaveOccurred = $false RequestAccepted = $false ImmediateEffectVerified = $false BeforeServiceStatus = $beforeServiceStatus AfterServiceStatus = $beforeServiceStatus Message = 'EndpointForge did not restart the agent because another running Windows service depends on it.' NextStep = 'Use your approved service-management process to review dependent services before trying again.' RecoveryGuidance = 'No service change was attempted.' } } $changeMayHaveOccurred = $true $null = & $getRemainingTimeout $controller.Stop() $controller.WaitForStatus( [System.ServiceProcess.ServiceControllerStatus]::Stopped, (& $getRemainingTimeout) ) $controller.Refresh() } elseif ($controller.Status -eq [System.ServiceProcess.ServiceControllerStatus]::StopPending) { $controller.WaitForStatus( [System.ServiceProcess.ServiceControllerStatus]::Stopped, (& $getRemainingTimeout) ) $controller.Refresh() } if ($controller.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running) { $changeMayHaveOccurred = $true $null = & $getRemainingTimeout $controller.Start() $controller.WaitForStatus( [System.ServiceProcess.ServiceControllerStatus]::Running, (& $getRemainingTimeout) ) $controller.Refresh() } $null = & $getRemainingTimeout $afterServiceStatus = $controller.Status.ToString() $verb = if ($Action -eq 'StartAgentService') { 'started' } else { 'restarted' } $message = if ($Agent -eq 'Intune' -and $Action -eq 'RestartAgentService') { "The Intune Management Extension service was $verb. Its check-in and any assigned work continue separately." } else { "The local management agent service was $verb and is running. Any management communication or assigned work continues separately." } return [pscustomobject][ordered]@{ Outcome = 'Completed' IsSuccessful = $afterServiceStatus -eq 'Running' ChangeMayHaveOccurred = $changeMayHaveOccurred RequestAccepted = $false ImmediateEffectVerified = $afterServiceStatus -eq 'Running' BeforeServiceStatus = $beforeServiceStatus AfterServiceStatus = $afterServiceStatus Message = $message NextStep = 'Wait for the management agent to finish its work, then confirm the device in your organization management console.' RecoveryGuidance = 'EndpointForge cannot undo management work triggered after the service starts. If the service stops again, review its local Windows and agent logs.' } } catch { if ($null -ne $controller) { try { $controller.Refresh() $afterServiceStatus = $controller.Status.ToString() } catch { $afterServiceStatus = $null } } $partial = $changeMayHaveOccurred -and $beforeServiceStatus -ne $afterServiceStatus return [pscustomobject][ordered]@{ Outcome = if ($partial) { 'PartiallyChanged' } else { 'Failed' } IsSuccessful = $false ChangeMayHaveOccurred = $changeMayHaveOccurred RequestAccepted = $false ImmediateEffectVerified = $false BeforeServiceStatus = $beforeServiceStatus AfterServiceStatus = $afterServiceStatus Message = 'The local management agent service action did not complete cleanly. EndpointForge does not assume the agent is working.' NextStep = 'Check the current service state and the agent local logs before trying another action.' RecoveryGuidance = 'If the service is stopped, use your approved Windows service process to start it. EndpointForge cannot roll back management work that may already have begun.' } } finally { $operationTimer.Stop() if ($null -ne $controller) { $controller.Dispose() } } } $worker = { param($InputData) $methodName = [string]$InputData.MethodName if ($methodName -notin @('RequestMachinePolicy', 'EvaluateMachinePolicy')) { return [pscustomobject]@{ Valid = $false; ReturnValue = -1 } } $cimManifestPath = [IO.Path]::Combine([string]$PSHOME, 'Modules', 'CimCmdlets', 'CimCmdlets.psd1') if (-not [IO.File]::Exists($cimManifestPath)) { return [pscustomobject]@{ Valid = $false; ReturnValue = -1 } } try { Microsoft.PowerShell.Core\Import-Module -Name $cimManifestPath -Force -ErrorAction Stop if ($methodName -eq 'RequestMachinePolicy') { $response = CimCmdlets\Invoke-CimMethod -Namespace 'root\ccm' -ClassName 'SMS_Client' ` -MethodName 'RequestMachinePolicy' -Arguments @{ uFlags = [uint32]0 } -ErrorAction Stop } else { $response = CimCmdlets\Invoke-CimMethod -Namespace 'root\ccm' -ClassName 'SMS_Client' ` -MethodName 'EvaluateMachinePolicy' -ErrorAction Stop } [pscustomobject]@{ Valid = $null -ne $response -and $null -ne $response.PSObject.Properties['ReturnValue'] ReturnValue = if ($null -ne $response -and $null -ne $response.PSObject.Properties['ReturnValue']) { [int64]$response.ReturnValue } else { -1 } } } catch { [pscustomobject]@{ Valid = $false; ReturnValue = -1 } } } try { $response = Invoke-EFIsolatedCheck -ScriptBlock $worker -InputData @{ MethodName = $Action } ` -TimeoutMilliseconds ($TimeoutSeconds * 1000) -StartupAllowanceMilliseconds 3000 ` -Activity 'The Configuration Manager client request' } catch { return [pscustomobject][ordered]@{ Outcome = 'Failed' IsSuccessful = $false ChangeMayHaveOccurred = $true RequestAccepted = $false ImmediateEffectVerified = $false BeforeServiceStatus = $null AfterServiceStatus = $null Message = 'EndpointForge could not confirm whether the Configuration Manager client accepted the request.' NextStep = 'Do not repeat the request immediately. Review the local Configuration Manager client logs and management console first.' RecoveryGuidance = 'The request may have reached the client before the result became unavailable. EndpointForge cannot cancel work that the client already accepted.' } } $valid = $null -ne $response -and (Test-EFPropertyPresent -InputObject $response -Name 'Valid') -and (Test-EFPropertyPresent -InputObject $response -Name 'ReturnValue') -and $response.Valid -is [bool] $returnValue = $null try { $returnValue = [int64]$response.ReturnValue } catch { $valid = $false } $accepted = $valid -and [bool]$response.Valid -and $returnValue -eq 0 if (-not $accepted) { return [pscustomobject][ordered]@{ Outcome = 'Failed' IsSuccessful = $false ChangeMayHaveOccurred = $valid RequestAccepted = $false ImmediateEffectVerified = $false BeforeServiceStatus = $null AfterServiceStatus = $null Message = 'The Configuration Manager client did not return a successful acceptance result.' NextStep = 'Review the local Configuration Manager client health and logs before trying another request.' RecoveryGuidance = 'No successful request was confirmed. If the client did accept work, EndpointForge cannot cancel it.' } } $friendlyAction = if ($Action -eq 'RequestMachinePolicy') { 'machine-policy download' } else { 'local machine-policy evaluation' } [pscustomobject][ordered]@{ Outcome = 'RequestAccepted' IsSuccessful = $true ChangeMayHaveOccurred = $true RequestAccepted = $true ImmediateEffectVerified = $false BeforeServiceStatus = $null AfterServiceStatus = $null Message = "The Configuration Manager client accepted the $friendlyAction request. This does not prove that policy synchronized or finished applying." NextStep = 'Allow the client time to work, then confirm the result in your Configuration Manager console or approved client logs.' RecoveryGuidance = 'An accepted request is asynchronous and cannot be rolled back by EndpointForge. Use your approved Configuration Manager process if downstream work must be investigated.' } } |