objectNotFound
objectNotFound

Reputation: 1783

How to get negative return codes from powershell to ansible

Iam trying to return a negative return code from a PowerShell script (.ps1) to Ansible. However the -ve codes that the script returns get converted into a large +ve integer for some reason. The script works fine if the return codes are +ve integers.

BTW The large +ve integer can be calculated like so:

4294967197 = 4294967296-99 (where -99 is the return code from PowerShell and 4294967296 = 4 * 1024**3 )

Appreciate any help. Thanks!

Details:

Also if i try this from the Windows Command Prompt it works !:

C:\> powershell -NoProfile %USERPROFILE%\Documents\ps_file_create.ps1

C:\> echo %errorlevel%
-99

This is what I have tried .. everything commented in the ansible/ps scripts below is what i have tried( I have also tried win_shell and get same results )

PowerShell Script (ps_file_create.ps1 ):

$return_code = -99  ## createfile $FileName
#return $return_code
#exit $return_code
$host.SetShouldExit($return_code)
exit

Ansible code:

  - name: Execute the PowerShell Script that returns a -ve return code 
    #win_command: powershell -NonInteractive -NoProfile Invoke-Expression -Command ".\ps_file_create.ps1" 
    #win_command: powershell -NonInteractive -NoProfile .\ps_file_create.ps1
    #win_command: powershell -NonInteractive -NoProfile -file ".\ps_file_create.ps1" 
    #win_command: powershell -NonInteractive -NoProfile -Command ".\ps_file_create.ps1" 
    win_command: powershell .\ps_file_create.ps1 
    args:
      chdir: '%USERPROFILE%\Documents'
    register: win_ps_out   
    ignore_errors: true       

  - name: Print Results of tasks
    debug:
      msg: 
        - "Powershell Out put: {{ win_ps_out  | to_nice_json}}"

Output:

TASK [Print Results of tasks]*****************************************************************
    ok: [myserver.xyz.abc.com] =>
      msg:
      - |-
        Powershell Out put: {
            "changed": true,
            "cmd": "powershell .\\ps_file_create.ps1",
            "delta": "0:00:00.532040",
            "end": "2020-04-20 01:32:30.515580",
            "failed": true,
            "msg": "non-zero return code",
            "rc": 4294967197,  ##  Expected -99 ##
            "start": "2020-04-20 01:32:29.983539",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "",
            "stdout_lines": []
        }

Upvotes: 3

Views: 3028

Answers (2)

stackprotector
stackprotector

Reputation: 13480

Long story short, here is a workaround. You can define conditional tasks or a conditional output like this:

---
- hosts: windows
  tasks:
  - name: Execute the PowerShell Script that returns a -ve return code
    win_command: powershell .\ps_file_create.ps1 
    args:
      chdir: '%USERPROFILE%\Documents'
    register: win_ps_out   
    ignore_errors: true       

  - name: Conditional task for positive rc
    debug:
      msg: "PowerShell rc: {{ win_ps_out.rc }}"
    when: win_ps_out.rc <= 2147483647

  - name: Conditional task for negative rc
    debug:
      msg: "PowerShell rc: {{ win_ps_out.rc - 4294967296 }}"
    when: win_ps_out.rc > 2147483647

  - name: Conditional output
    debug:
      msg: "PowerShell rc: {% if win_ps_out.rc > 2147483647 %}{{ win_ps_out.rc - 4294967296 }}{% else %}{{ win_ps_out.rc }}{% endif %}"

Output for a return code of 42:

TASK [Conditional task for positive rc] ****************************************
ok: [192.168.0.200] => {
    "msg": "PowerShell rc: 42"
}

TASK [Conditional task for negative rc] ****************************************
skipping: [192.168.0.200]

TASK [Conditional output] ******************************************************
ok: [192.168.0.200] => {
    "msg": "PowerShell rc: 42"
}

And for a return code of -99:

TASK [Conditional task for positive rc] ****************************************
skipping: [192.168.0.200]

TASK [Conditional task for negative rc] ****************************************
ok: [192.168.0.200] => {
    "msg": "PowerShell rc: -99"
}

TASK [Conditional output] ******************************************************
ok: [192.168.0.200] => {
    "msg": "PowerShell rc: -99"
}

Background

The return code shown in the question is "correct". The binary representation of a negative integer value is commonly calculated by the two's complement method. E. g., the binary representation of -99 (using 32 bits) is:

‭1111 1111 1111 1111 1111 1111 1001 1101‬

But from reading these bits, you don't know if they represent a negative number in two's complement form or just a large positive number. You can interpret these bits either as the small negative number -99 or as the large positive number ‭4.294.967.197‬, which is what you are seeing in your output.

PowerShell returns exactly those 4 bytes shown above. You can confirm that PowerShell uses 32-bit exit codes even on a 64-bit operating system:

PS C:\> [Environment]::Is64BitProcess -and [Environment]::Is64BitOperatingSystem
True
PS C:\> $process = Start-Process powershell -ArgumentList "exit 0xffffffff" -Wait -PassThru
PS C:\> $process.ExitCode
-1
PS C:\> $process = Start-Process powershell -ArgumentList "exit 0x1ffffffff" -Wait -PassThru
PS C:\> $process.ExitCode
0
PS C:\> $process.ExitCode.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

You can see that the exit code is of type Int32 and also that PowerShell has an exit code of 0 if the "real" exit code exceeds the range of Int32.

So PowerShell does nothing wrong here. It is up to the user who evaluates those 4 bytes to interpret them as a signed or an unsigned integer. So it is up to ansible.

As far as I know, you don't have many possibilities to influence ansible on how to capture results from a task. You can only use filters and algorithms to process the results.

There is no such thing as type casting as far as I know ({{ win_ps_out.rc | int }} is actually a jinja2 filter, not a cast). And ansible does not seem to care about variable sizes. E. g., ansible is not aware of int32 or int64. Just int, and that can be very large numbers, even larger than int64 could hold. So what ansible does, is taking the 4 bytes from PowerShell and storing them as an integer with additional - I don't know how many - leading zeros. This would be an example for 64 bits:

‭0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1001 1101‬

Now, this number cannot be interpreted as a negative number in two's complement form, because the most significant bit is set to 0. It can only be interpreted as 4.294.967.197, what happens in your case.

It is now up to you to handle this value. On the top of my answer I present two workarounds to process the returned value and treat it as a signed integer with a size of 32 bits.

Upvotes: 1

JPBlanc
JPBlanc

Reputation: 72640

What happened if in your PowerShell script you just try :

exit -99

in spite of using $host.SetShouldExit($return_code)

When using cmd.exe, I try it the result seems to be Ok :

c:\windows\system32\WindowsPowerShell\v1.0\powershell.exe -command "exit -99"
echo %errorlevel%
-99

In fact your code gives the same result :

c:\windows\system32\WindowsPowerShell\v1.0\powershell.exe -command "$host.SetShouldExit(-99)"
echo %errorlevel%
-99

You should have a look on Ansible side where it seems to be interpreted as a 64 bits, on PowerShell side it'a a 32 bits.

Upvotes: 1

Related Questions