Server IP : 85.214.239.14 / Your IP : 3.141.193.54 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/ansible/windows/plugins/modules/ |
Upload File : |
#!powershell # Copyright: (c) 2021, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) #AnsibleRequires -PowerShell Ansible.ModuleUtils.AddType #AnsibleRequires -CSharpUtil Ansible.Basic #AnsibleRequires -PowerShell ..module_utils.Process $spec = @{ options = @{ arguments = @{ type = 'list'; elements = 'str' } chdir = @{ type = 'str' } creates = @{ type = 'str' } depth = @{ type = 'int'; default = 2 } error_action = @{ type = 'str'; choices = 'silently_continue', 'continue', 'stop'; default = 'continue' } executable = @{ type = 'str' } parameters = @{ type = 'dict' } removes = @{ type = 'str' } script = @{ type = 'str'; required = $true } } supports_check_mode = $true } $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) $module.Result.result = @{} $module.Result.host_out = '' $module.Result.host_err = '' $module.Result.output = @() $module.Result.error = @() $module.Result.warning = @() $module.Result.verbose = @() $module.Result.debug = @() $module.Result.information = @() $stdPinvoke = @' using Microsoft.Win32.SafeHandles; using System; using System.Runtime.InteropServices; namespace Ansible.Windows.WinPowerShell { public class NativeMethods { [DllImport("Kernel32.dll", SetLastError = true)] public static extern bool AllocConsole(); [DllImport("Kernel32.dll", SetLastError = true)] public static extern IntPtr GetConsoleWindow(); [DllImport("Kernel32.dll")] public static extern IntPtr GetStdHandle( int nStdHandle); [DllImport("Kernel32.dll", SetLastError = true)] public static extern bool FreeConsole(); [DllImport("Kernel32.dll")] public static extern bool SetStdHandle( int nStdHandle, IntPtr hHandle); } } '@ Add-CSharpType -AnsibleModule $module -References $stdPinvoke, @' using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; using System.Security; namespace Ansible.Windows.WinPowerShell { public class Host : PSHost { private readonly PSHost PSHost; private readonly HostUI HostUI; public Host(PSHost host, StreamWriter stdout, StreamWriter stderr) { PSHost = host; HostUI = new HostUI(stdout, stderr); } public override CultureInfo CurrentCulture { get { return PSHost.CurrentCulture; } } public override CultureInfo CurrentUICulture { get { return PSHost.CurrentUICulture; } } public override Guid InstanceId { get { return PSHost.InstanceId; } } public override string Name { get { return PSHost.Name; } } public override PSHostUserInterface UI { get { return HostUI; } } public override Version Version { get { return PSHost.Version; } } public override void EnterNestedPrompt() { PSHost.EnterNestedPrompt(); } public override void ExitNestedPrompt() { PSHost.ExitNestedPrompt(); } public override void NotifyBeginApplication() { PSHost.NotifyBeginApplication(); } public override void NotifyEndApplication() { PSHost.NotifyEndApplication(); } public override void SetShouldExit(int exitCode) { PSHost.SetShouldExit(exitCode); } } public class HostUI : PSHostUserInterface { private StreamWriter _stdout; private StreamWriter _stderr; public HostUI(StreamWriter stdout, StreamWriter stderr) { _stdout = stdout; _stderr = stderr; } public override PSHostRawUserInterface RawUI { get { return null; } } public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions) { throw new MethodInvocationException("PowerShell is in NonInteractive mode. Read and Prompt functionality is not available."); } public override int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice) { throw new MethodInvocationException("PowerShell is in NonInteractive mode. Read and Prompt functionality is not available."); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { throw new MethodInvocationException("PowerShell is in NonInteractive mode. Read and Prompt functionality is not available."); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { throw new MethodInvocationException("PowerShell is in NonInteractive mode. Read and Prompt functionality is not available."); } public override string ReadLine() { throw new MethodInvocationException("PowerShell is in NonInteractive mode. Read and Prompt functionality is not available."); } public override SecureString ReadLineAsSecureString() { throw new MethodInvocationException("PowerShell is in NonInteractive mode. Read and Prompt functionality is not available."); } public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { _stdout.Write(value); } public override void Write(string value) { _stdout.Write(value); } public override void WriteDebugLine(string message) { _stdout.WriteLine(String.Format("DEBUG: {0}", message)); } public override void WriteErrorLine(string value) { _stderr.WriteLine(value); } public override void WriteLine(string value) { _stdout.WriteLine(value); } public override void WriteProgress(long sourceId, ProgressRecord record) {} public override void WriteVerboseLine(string message) { _stdout.WriteLine(String.Format("VERBOSE: {0}", message)); } public override void WriteWarningLine(string message) { _stdout.WriteLine(String.Format("WARNING: {0}", message)); } } } '@ Function Get-StdHandle { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateSet('Stdout', 'Stderr')] [string] $Stream ) $id, $dotnet = switch ($Stream) { Stdout { -11, [Console]::Out } Stderr { -12, [Console]::Error } } $handle = [Ansible.Windows.WinPowerShell.NativeMethods]::GetStdHandle($id) [PSCustomObject]@{ Stream = $Stream NET = $dotnet Raw = $handle } } Function Set-StdHandle { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateSet('Stdout', 'Stderr')] [string] $Stream, [IO.TextWriter] $NET, [IntPtr] $Raw ) $id, $meth = switch ($Stream) { Stdout { -11; [Console]::SetOut($NET) } Stderr { -12; [Console]::SetError($NET) } } # .NET does not actually affect the std handle on the process, we need to call SetStdHandle so any child processes # spawned with Start-Process -NoNewWindow will use our custom pipe. [void][Ansible.Windows.WinPowerShell.NativeMethods]::SetStdHandle($id, $Raw) } Function Convert-OutputObject { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [AllowNull()] [object] $InputObject, [Parameter(Mandatory = $true)] [int] $Depth ) begin { $childDepth = $Depth - 1 $isType = { [CmdletBinding()] param ( [Object] $InputObject, [Type] $Type ) if ($InputObject -is $Type) { return $true } $psTypes = @($InputObject.PSTypeNames | ForEach-Object -Process { $_ -replace '^Deserialized.' }) $Type.FullName -in $psTypes } } process { if ($null -eq $InputObject) { $null } elseif ((&$isType -InputObject $InputObject -Type ([Enum])) -and $Depth -ge 0) { # ToString() gives the human readable value but I thought it better to give some more context behind # these types. @{ Type = ($InputObject.PSTypeNames[0] -replace '^Deserialized.') String = $InputObject.ToString() Value = [int]$InputObject } } elseif ($InputObject -is [DateTime]) { # The offset is based on the Kind value # Unspecified leaves it off # UTC set it to Z # Local sets it to the local timezone $InputObject.ToString('o') } elseif (&$isType -InputObject $InputObject -Type ([DateTimeOffset])) { # If this is a deserialized object (from an executable) we need recreate a live DateTimeOffset if ($InputObject -isnot [DateTimeOffset]) { $InputObject = New-Object -TypeName DateTimeOffset $InputObject.DateTime, $InputObject.Offset } $InputObject.ToString('o') } elseif (&$isType -InputObject $InputObject -Type ([Type])) { if ($Depth -lt 0) { $InputObject.FullName } else { # This type is very complex with circular properties, only return somewhat useful properties. # BaseType might be a string (serialized output), try and convert it back to a Type if possible. $baseType = $InputObject.BaseType -as [Type] if ($baseType) { $baseType = Convert-OutputObject -InputObject $baseType -Depth $childDepth } @{ Name = $InputObject.Name FullName = $InputObject.FullName AssemblyQualifiedName = $InputObject.AssemblyQualifiedName BaseType = $baseType } } } elseif ($InputObject -is [string]) { # Get the BaseObject to strip out any ETS properties $InputObject.PSObject.BaseObject } elseif (&$isType -InputObject $InputObject -Type ([switch])) { $InputObject.IsPresent } elseif ($InputObject.GetType().IsValueType) { # We want to display just this value and not any properties it has (if any). $InputObject.PSObject.BaseObject } elseif ($Depth -lt 0) { # This must occur after the above to ensure ints and other ValueTypes are preserved as is. [string]$InputObject } elseif ($InputObject -is [Collections.IList]) { , @(foreach ($obj in $InputObject) { Convert-OutputObject -InputObject $obj -Depth $childDepth }) } elseif ($InputObject -is [Collections.IDictionary]) { $newObj = @{} # Replicate ConvertTo-Json, props are replaced by keys if they share the same name. We only want ETS # properties as well. foreach ($prop in $InputObject.PSObject.Properties) { if ($prop.MemberType -notin @('AliasProperty', 'ScriptProperty', 'NoteProperty')) { continue } $newObj[$prop.Name] = Convert-OutputObject -InputObject $prop.Value -Depth $childDepth } foreach ($kvp in $InputObject.GetEnumerator()) { $newObj[$kvp.Key] = Convert-OutputObject -InputObject $kvp.Value -Depth $childDepth } $newObj } else { $newObj = @{} foreach ($prop in $InputObject.PSObject.Properties) { $newObj[$prop.Name] = Convert-OutputObject -InputObject $prop.Value -Depth $childDepth } $newObj } } } Function Format-Exception { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowNull()] $Exception ) if (-not $Exception) { return $null } elseif ($Exception -is [Management.Automation.RemoteException]) { # When using a separate process the exceptions are a RemoteException, we want to get the info on the actual # exception. return Format-Exception -Exception $Exception.SerializedRemoteException } $type = if ($Exception -is [Exception]) { $Exception.GetType().FullName } else { # This is a RemoteException, we want to report the original non-serialized type. $Exception.PSTypeNames[0] -replace '^Deserialized.' } @{ message = $Exception.Message type = $type help_link = $Exception.HelpLink source = $Exception.Source hresult = $Exception.HResult inner_exception = Format-Exception -Exception $Exception.InnerException } } Function Test-AnsiblePath { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Path ) # Certain system files fail on Test-Path due to it being locked, we can still get the attributes though. try { [void][System.IO.File]::GetAttributes($Path) return $true } catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException] { return $false } catch { # When testing a path like Cert:\LocalMachine\My, System.IO.File will # not work, we just revert back to using Test-Path for this return Test-Path -Path $Path } } Function New-AnonymousPipe { [CmdletBinding()] param () $utf8NoBom = New-Object -TypeName Text.UTF8Encoding -ArgumentList $false $server = New-Object -TypeName IO.Pipes.AnonymousPipeServerStream -ArgumentList 'In', 'Inheritable' $client = New-Object -TypeName IO.Pipes.AnonymousPipeClientStream -ArgumentList 'Out', $server.ClientSafePipeHandle $clientWriter = New-Object -TypeName IO.StreamWriter -ArgumentList $client, $utf8NoBom $clientWriter.AutoFlush = $true # Ensures the data stays in sync when dealing with subprocesses. # Create the background task that will constantly read from the pipe and append to our StringBuilder until the pipe # is closed. It also closes the pipe once finished so we don't have to. Without this the pipe buffer can become # full and hang the script. $sb = New-Object -TypeName Text.StringBuilder $ps = [PowerShell]::Create() [void]$ps.AddScript(@' [CmdletBinding()] param ( [Text.StringBuilder] $StringBuilder, [IO.Pipes.AnonymousPipeServerStream] $Server, [Text.Encoding] $Encoding ) $sr = New-Object -TypeName IO.StreamReader -ArgumentList $Server, $Encoding try { $buffer = New-Object -TypeName char[] -ArgumentList $Server.InBufferSize while ($read = $sr.Read($buffer, 0, $buffer.Length)) { [void]$StringBuilder.Append($buffer, 0, $read) } } finally { $sr.Dispose() } '@).AddParameters(@{ StringBuilder = $sb Server = $server Encoding = $utf8NoBom }) $task = $ps.BeginInvoke() [PSCustomObject]@{ PowerShell = $ps Task = $task Output = $sb Client = $clientWriter ClientString = $server.GetClientHandleAsString() } } $creates = $module.Params.creates if ($creates -and (Test-AnsiblePath -Path $creates)) { $module.Result.msg = "skipped, since $creates exists" $module.ExitJson() } $removes = $module.Params.removes if ($removes -and -not (Test-AnsiblePath -Path $removes)) { $module.Result.msg = "skipped, since $removes does not exist" $module.ExitJson() } # Check if the script has [CmdletBinding(SupportsShouldProcess)] on it try { $scriptAst = [ScriptBlock]::Create($module.Params.script).Ast } catch [System.Management.Automation.ParseException] { # Trying to parse pwsh 7 code may fail if using new syntax not available in # WinPS. Need to fallback to a more rudimentary scanner. # https://github.com/ansible-collections/ansible.windows/issues/452 $scriptAst = $null } $supportsShouldProcess = $false if ($scriptAst -and $scriptAst -is [Management.Automation.Language.ScriptBlockAst] -and $scriptAst.ParamBlock.Attributes) { $supportsShouldProcess = [bool]($scriptAst.ParamBlock.Attributes | Where-Object { $_.TypeName.Name -eq 'CmdletBinding' } | Select-Object -First 1 | ForEach-Object -Process { $_.NamedArguments | Where-Object { $_.ArgumentName -eq 'SupportsShouldProcess' -and ($_.ExpressionOmitted -or $_.Argument.ToString() -eq '$true') } }) } elseif (-not $scriptAst) { $supportsShouldProcess = $module.Params.script -match '\[CmdletBinding\((?:[\w=\$]+,\s*)?SupportsShouldProcess(?:=\$true)?(?:,\s*[\w=\$]+)?\)\]' } if ($module.CheckMode -and -not $supportsShouldProcess) { $module.Result.changed = $true $module.Result.msg = "skipped, running in check mode" $module.ExitJson() } $runspace = $null $processId = $null $newStdout = New-AnonymousPipe $newStderr = New-AnonymousPipe $freeConsole = $false try { $oldStdout = Get-StdHandle -Stream Stdout $oldStderr = Get-StdHandle -Stream Stderr if ($module.Params.executable) { if ($PSVersionTable.PSVersion -lt [version]'5.0') { $module.FailJson("executable requires PowerShell 5.0 or newer") } # Neither Start-Process or Diagnostics.Process give us the ability to create a process with a new console and # the ability to inherit handles so we use own home grown CreateProcess wrapper. $applicationName = Resolve-ExecutablePath -FilePath $module.Params.executable $commandLine = ConvertTo-EscapedArgument -InputObject $module.Params.executable if ($module.Params.arguments) { $escapedArguments = @($module.Params.arguments | ConvertTo-EscapedArgument) $commandLine += " $($escapedArguments -join ' ')" } # While we could attach the stdout/stderr pipes here we would capture the startup info and prompt that # powershell will output. Instead we set the console as part of the pipeline we run. $si = [Ansible.Windows.Process.StartupInfo]@{ WindowStyle = 'Hidden' # Useful when debugging locally, doesn't really matter in normal Ansible. } $pi = [Ansible.Windows.Process.ProcessUtil]::NativeCreateProcess( $applicationName, $commandLIne, $null, $null, $true, # Required so the child process can inherit our anon pipes. 'CreateNewConsole', # Ensures we don't mess with the current console output. $null, $null, $si ) $processId = $pi.ProcessId $pi.Dispose() } # Using a custom host allows us to capture any host UI calls through our own output. $runspaceHost = New-Object -TypeName Ansible.Windows.WinPowerShell.Host -ArgumentList $Host, $newStdout.Client, $newStderr.Client if ($processId) { $connInfo = [System.Management.Automation.Runspaces.NamedPipeConnectionInfo]$processId # In case a user specified an executable that does not support the PSHost named pipe that PowerShell uses we # specify a timeout so the module does not hang. $connInfo.OpenTimeout = 5000 $runspace = [RunspaceFactory]::CreateRunspace($runspaceHost, $connInfo) } else { $runspace = [RunspaceFactory]::CreateRunspace($runspaceHost) } $runspace.Open() $ps = [PowerShell]::Create() $ps.Runspace = $runspace $ps.Runspace.SessionStateProxy.SetVariable('Ansible', [PSCustomObject]@{ PSTypeName = 'Ansible.Windows.WinPowerShell.Module' CheckMode = $module.CheckMode Verbosity = $module.Verbosity Result = @{} Diff = @{} Changed = $true Failed = $false Tmpdir = $module.Tmpdir }) $eap = switch ($module.Params.error_action) { 'stop' { 'Stop' } 'continue' { 'Continue' } 'silently_continue' { 'SilentlyContinue' } } $ps.Runspace.SessionStateProxy.SetVariable('ErrorActionPreference', [Management.Automation.ActionPreference]$eap) if ($processId) { # If we are running in a new process we need to set the various console encoding values to UTF-8 to ensure a # consistent encoding experience when PowerShell is running native commands and getting the output back. We # also need to redirect the stdout/stderr pipes to our anonymous pipe so we can capture any native console # output from .NET or calling a native application with 'Start-Process -NoNewWindow'. [void]$ps.AddScript(@' [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] $StdoutHandle, [Parameter(Mandatory=$true)] [String] $StderrHandle, [Parameter(Mandatory=$true)] [String] $SetStdPInvoke, [Parameter(Mandatory=$true)] [String] $SetScriptBlock, [Parameter(Mandatory=$true)] [String] $AddTypeCode, [Parameter(Mandatory=$true)] [String] $Tmpdir ) # Using Add-Type here leaves an empty folder for some reason, our code does not and also allows us to control the # temp directory used. &([ScriptBlock]::Create($AddTypeCode)) -References $SetStdPInvoke -TempPath $Tmpdir $setHandle = [ScriptBlock]::Create($SetScriptBlock) $utf8NoBom = New-Object -TypeName Text.UTF8Encoding -ArgumentList $false # Make sure our console encoding values are all set to UTF-8 for a consistent experience. $OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = $utf8NoBom # Set the stdout/stderr for both .NET and natively to our anonymous pipe. @{Name = 'Stdout'; Handle = $StdoutHandle}, @{Name = 'Stderr'; Handle = $StderrHandle} | ForEach-Object -Process { $pipe = New-Object -TypeName IO.Pipes.AnonymousPipeClientStream -ArgumentList 'Out', $_.Handle $writer = New-Object -TypeName IO.StreamWriter -ArgumentList $pipe, $utf8NoBom $writer.AutoFlush = $true # Ensures we data in the correct order. &$setHandle -Stream $_.Name -NET $writer -Raw $pipe.SafePipeHandle.DangerousGetHandle() } '@, $true).AddParameters(@{ StdoutHandle = $newStdout.ClientString StderrHandle = $newStderr.ClientString SetStdPInvoke = $stdPinvoke SetScriptBlock = ${function:Set-StdHandle} AddTypeCode = ${function:Add-CSharpType} TmpDir = $module.Tmpdir }).AddStatement() } else { # The psrp connection plugin doesn't have a console so we need to create one ourselves. if ([Ansible.Windows.WinPowerShell.NativeMethods]::GetConsoleWindow() -eq [IntPtr]::Zero) { $freeConsole = [Ansible.Windows.WinPowerShell.NativeMethods]::AllocConsole() } # Else we are running in the same process, we need to redirect the console and .NET output pipes to our # anonymous pipe. We shouldn't have to set the encoding, the module wrapper already does this. Set-StdHandle -Stream Stdout -NET $newStdout.Client -Raw $newStdout.Client.BaseStream.SafePipeHandle.DangerousGetHandle() Set-StdHandle -Stream Stderr -NET $newStderr.Client -Raw $newStderr.Client.BaseStream.SafePipeHandle.DangerousGetHandle() $utf8NoBom = New-Object -TypeName Text.UTF8Encoding -ArgumentList $false $OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = $utf8NoBom } if ($module.Params.chdir) { [void]$ps.AddCommand('Set-Location').AddParameter('LiteralPath', $module.Params.chdir).AddStatement() } [void]$ps.AddScript($module.Params.script) # We copy the existing parameter dictionary and add/modify the Confirm/WhatIf parameters if the script supports # processing. We do a copy to avoid modifying the original Params dictionary just for safety. $parameters = @{} if ($module.Params.parameters) { foreach ($kvp in $module.Params.parameters.GetEnumerator()) { $parameters[$kvp.Key] = $kvp.Value } } if ($supportsShouldProcess) { # We do this last to ensure we take precedence over any user inputted settings. $parameters.Confirm = $false # Ensure we don't block on any confirmation prompts $parameters.WhatIf = $module.CheckMode } if ($parameters) { [void]$ps.AddParameters($parameters) } # We cannot natively call a generic function so need to resort to reflection to get the method we know is there # and turn it into an invocable method. We do this so we can call the overload that takes in an IList for the # output which means we don't loose anything from before a terminating exception was raised. $psOutput = [Collections.Generic.List[Object]]@() $invokeMethod = $ps.GetType().GetMethods('Public, Instance, InvokeMethod') | Where-Object { if ($_.Name -ne 'Invoke' -or $_.ReturnType -ne [void] -or -not $_.ContainsGenericParameters) { return $false } $parameters = $_.GetParameters() ( $parameters.Count -eq 2 -and $parameters[0].ParameterType -eq [Collections.IEnumerable] -and $parameters[1].ParameterType.Namespace -eq 'System.Collections.Generic' -and $parameters[1].ParameterType.Name -eq 'IList`1' ) } $invoke = $invokeMethod.MakeGenericMethod([object]) try { $invoke.Invoke($ps, @(@(), $psOutput)) } catch [Management.Automation.RuntimeException] { # $ErrorActionPrefrence = 'Stop' and an error was raised # OR # General exception was raised in the script like 'throw "error"'. # We treat these as failures in the script and return them back to the user. $module.Result.failed = $true $ps.Streams.Error.Add($_.Exception.ErrorRecord) } # Get the internal Ansible variable that can contain code specific information. $result = $runspace.SessionStateProxy.GetVariable('Ansible') } finally { if (-not $processId) { $oldStdout, $oldStderr | ForEach-Object -Process { Set-StdHandle -Stream $_.Stream -NET $_.NET -Raw $_.Raw } } if ($runspace) { $runspace.Dispose() } if ($processId) { Stop-Process -Id $processId -Force } $newStdout, $newStderr | ForEach-Object -Process { $_.Client.Dispose() [void]$_.PowerShell.EndInvoke($_.Task) } if ($freeConsole) { [void][Ansible.Windows.WinPowerShell.NativeMethods]::FreeConsole() } } $module.Result.host_out = $newStdout.Output.ToString() $module.Result.host_err = $newStderr.Output.ToString() $module.Result.result = Convert-OutputObject -InputObject $result.Result -Depth $module.Params.depth $module.Result.changed = $result.Changed $module.Result.failed = $module.Result.failed -or $result.Failed # If the diff was somehow changed to something else we cannot set it to the # module output diff so check if it's still a dict. if ($result.Diff -is [System.Collections.IDictionary]) { foreach ($kvp in $result.Diff.GetEnumerator()) { $module.Diff[$kvp.Key] = Convert-OutputObject -InputObject $kvp.Value -Depth $module.Params.depth } } # We process the output outselves to flatten anything beyond the depth and deal with certain problematic types with # json serialization. $module.Result.output = Convert-OutputObject -InputObject $psOutput -Depth $module.Params.depth $module.Result.error = @($ps.Streams.Error | ForEach-Object -Process { $err = @{ output = ($_ | Out-String) error_details = $null exception = Format-Exception -Exception $_.Exception target_object = Convert-OutputObject -InputObject $_.TargetObject -Depth $module.Params.depth category_info = @{ category = [string]$_.CategoryInfo.Category category_id = [int]$_.CategoryInfo.Category activity = $_.CategoryInfo.Activity reason = $_.CategoryInfo.Reason target_name = $_.CategoryInfo.TargetName target_type = $_.CategoryInfo.TargetType } fully_qualified_error_id = $_.FullyQualifiedErrorId script_stack_trace = $_.ScriptStackTrace pipeline_iteration_info = $_.PipelineIterationInfo } if ($_.ErrorDetails) { $err.error_details = @{ message = $_.ErrorDetails.Message recommended_action = $_.ErrorDetails.RecommendedAction } } $err }) 'debug', 'verbose', 'warning' | ForEach-Object -Process { $module.Result.$_ = @($ps.Streams.$_ | Select-Object -ExpandProperty Message) } # Use Select-Object as Information may not be present on earlier pwsh version (<v5). $module.Result.information = @($ps.Streams | Select-Object -ExpandProperty Information -ErrorAction SilentlyContinue | ForEach-Object -Process { @{ message_data = Convert-OutputObject -InputObject $_.MessageData -Depth $module.Params.depth source = $_.Source time_generated = $_.TimeGenerated.ToUniversalTime().ToString('o') tags = @($_.Tags) } } ) $module.ExitJson()