diff --git a/changelogs/fragments/psrp-utf8-stdio.yaml b/changelogs/fragments/psrp-utf8-stdio.yaml new file mode 100644 index 0000000000..25452d992b --- /dev/null +++ b/changelogs/fragments/psrp-utf8-stdio.yaml @@ -0,0 +1,2 @@ +bugfixes: +- psrp - Fix UTF-8 output - https://github.com/ansible/ansible/pull/46998 diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 index e952a7ab21..263ea55883 100644 --- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 @@ -156,6 +156,23 @@ namespace Ansible StringBuilder lpBuffer, out IntPtr lpFilePart); + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GetConsoleWindow(); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AllocConsole(); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool FreeConsole(); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleCP( + UInt32 wCodePageID); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleOutputCP( + UInt32 wCodePageID); + [DllImport("shell32.dll", SetLastError = true)] static extern IntPtr CommandLineToArgvW( [MarshalAs(UnmanagedType.LPWStr)] @@ -252,6 +269,16 @@ namespace Ansible if (environmentString != null) lpEnvironment = Marshal.StringToHGlobalUni(environmentString.ToString()); + // Create console if needed to be inherited by child process + bool isConsole = false; + if (GetConsoleWindow() == IntPtr.Zero) { + isConsole = AllocConsole(); + + // Set console input/output codepage to UTF-8 + SetConsoleCP(65001); + SetConsoleOutputCP(65001); + } + // Create new process and run StringBuilder argument_string = new StringBuilder(lpCommandLine); PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); @@ -270,6 +297,11 @@ namespace Ansible throw new Win32Exception("Failed to create new process"); } + // Destroy console if we created it + if (isConsole) { + FreeConsole(); + } + // Setup the output buffers and get stdout/stderr FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, 4096); StreamReader stdout = new StreamReader(stdout_fs, utf8_encoding, true, 4096); diff --git a/test/integration/targets/connection_psrp/tests.yml b/test/integration/targets/connection_psrp/tests.yml index c87d339888..ad700536dc 100644 --- a/test/integration/targets/connection_psrp/tests.yml +++ b/test/integration/targets/connection_psrp/tests.yml @@ -59,3 +59,27 @@ assert: that: - async_out.stdout_lines == ["abc"] + + - name: Output unicode characters from Powershell using PSRP + win_command: "powershell.exe -ExecutionPolicy ByPass -Command \"Write-Host '\U0001F4A9'\"" + register: command_unicode_output + + - name: Assert unicode output + assert: + that: + - command_unicode_output is changed + - command_unicode_output.rc == 0 + - "command_unicode_output.stdout == '\U0001F4A9\n'" + - command_unicode_output.stderr == '' + + - name: Output unicode characters from Powershell using PSRP + win_shell: "Write-Host '\U0001F4A9'" + register: shell_unicode_output + + - name: Assert unicode output + assert: + that: + - shell_unicode_output is changed + - shell_unicode_output.rc == 0 + - "shell_unicode_output.stdout == '\U0001F4A9\n'" + - shell_unicode_output.stderr == '' diff --git a/test/integration/targets/win_module_utils/library/command_util_test.ps1 b/test/integration/targets/win_module_utils/library/command_util_test.ps1 index 0a0826cd54..3515112e38 100644 --- a/test/integration/targets/win_module_utils/library/command_util_test.ps1 +++ b/test/integration/targets/win_module_utils/library/command_util_test.ps1 @@ -76,6 +76,12 @@ Assert-Equals -actual $actual.rc -expected 0 Assert-Equals -actual $actual.stdout -expected "stdout `r`n" Assert-Equals -actual $actual.stderr -expected "stderr `r`n" +$test_name = "Test UTF8 output from stdout stream" +$actual = Run-Command -command "powershell.exe -ExecutionPolicy ByPass -Command `"Write-Host '💩'`"" +Assert-Equals -actual $actual.rc -expected 0 +Assert-Equals -actual $actual.stdout -expected "💩`n" +Assert-Equals -actual $actual.stderr -expected "" + $test_name = "test default environment variable" Set-Item -Path env:TESTENV -Value "test" $actual = Run-Command -command "cmd.exe /c set"