Last active
March 10, 2026 00:15
-
-
Save dlenski/fb38419ca862bb6d2f718726113eb29e to your computer and use it in GitHub Desktop.
Quote Python strings as safe string literals for PowerShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| r'''Quote Python strings in such a way that they can be used safely as string | |
| literals in a Windows PowerShell script. | |
| The approach suggested in https://stackoverflow.com/a/1056978 seems to be the | |
| least-insane way to safely quote non-ASCII characters, along with | |
| https://stackoverflow.com/a/60825495 as a useful refinement for PowerShell | |
| v6.0+, where a slightly less verbose syntax for unicode escapes is possible. | |
| PowerShell string literal syntax is different from basically every other | |
| modern programming language, and not particularly well-documented. | |
| A particularly crazy mis-feature is that "smart quote" characters (non-ASCII quotation | |
| characters!) are treated as equivalent to normal ASCII quotation characters, and thus | |
| must be escaped. 😵💫 | |
| See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules | |
| and https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_special_characters | |
| for some official documentation in Microsoft's very non-linear style. | |
| ''' | |
| import re | |
| # Characters that need quoting/escaping in double-quoted strings | |
| # in PowerShell: | |
| # | |
| # ' ' (32): safe | |
| # '!' (33): safe | |
| # '"' (34): needs doubling or `-escaping | |
| # '#' (35): safe | |
| # '$' (36): needs `-escaping | |
| # '%'-'_' (37-95): safe | |
| # '`' (96): needs doubling or `-escaping | |
| # 'a'-'~' (97-126): safe | |
| # '\n\r\t\0\a\b\f\v': use ` instead of \ | |
| # Rest of UTF-8 BMP: "$([char]codepoint)" | |
| # Rest of UTF-8: "$([char]::ConvertFromUtf32(codepoint))" | |
| # | |
| # PowerShell 6+ only: | |
| # '\x1b': use `e | |
| # Rest of UTF-8: `u{hex codepoint} | |
| _NEED_PS_QUOTE = re.compile(r'[^ !#%-_a-~]') | |
| def _ps_quote_compat(m): | |
| c = m.group(0) | |
| if c in '"$`': | |
| return f'`{c}' | |
| elif (o := '\n\r\t\0\a\b\f\v'.find(c)) >= 0: # \e only exists in PowerShell 6+ | |
| return f'`{"nrt0abfv"[o]}' | |
| elif (o := ord(c)) <= 0xffff: | |
| return f'$([char]{o})' | |
| else: | |
| return f'$([char]::ConvertFromUtf32({o}))' | |
| def _ps_quote_v6(m): | |
| c = m.group(0) | |
| if c in '"$`': | |
| return f'`{c}' | |
| elif (o := '\n\r\t\0\a\b\f\v\x1b'.find(c)) >= 0: | |
| return f'`{"nrt0abfve"[o]}' | |
| else: | |
| return '`u{%X}' % ord(c) | |
| def psquote(s: str, v6: bool = False): | |
| '''Quote and escape a Python string in such a way that it can be | |
| used safely as a string literal in a Windows PowerShell script. | |
| All non-ASCII characters will be escaped, as well as characters | |
| that are parsed specially by PowerShell. | |
| Using v6 gives a slightly more concise output for non-ASCII | |
| characters, but is only compatible with PowerShell v6.0+ | |
| ''' | |
| return '"' + _NEED_PS_QUOTE.sub(_ps_quote_v6 if v6 else _ps_quote_compat, s) + '"' | |
| if __name__ == '__main__': | |
| assert psquote('foo$(\u03BC)bar\n\U0001F973`', v6=False) == '"foo`$($([char]956))bar`n$([char]::ConvertFromUtf32(129395))``"' | |
| assert psquote('foo$(\u03BC)bar\n\U0001F973`', v6=True) == '"foo`$(`u{3BC})bar`n`u{1F973}``"' | |
| print("All tests passed \U0001F973") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment