Created
September 9, 2020 23:03
-
-
Save mklement0/d80c80c5ee9b627cbecfccc4ce073c3b to your computer and use it in GitHub Desktop.
PowerShell script/function that executes a script block with a given culture and UI culture in effect
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
<# | |
NOTE: | |
It is BEST TO DOT-SOURCE (.) THIS SCRIPT, which defines a function of the same | |
name for later use. | |
While it is possible to invoke this script directly, its help can then only | |
be invoked with the -? switch, providing only terse help, | |
whereas dot-sourcing provides full Get-Help integration. | |
Similarly, only with dot-sourcing do you get tab completion. | |
Inspired by: https://rkeithhill.wordpress.com/2009/10/21/windows-powershell-2-0-string-localization/ | |
#> | |
function Use-Culture { | |
<# | |
.SYNOPSIS | |
Invokes a script with a given culture and UI culture in effect. | |
.DESCRIPTION | |
Facilitates experimenting with culture-sensitive operations, by invoking a | |
given script block with one or more given cultures temporarily in effect. | |
Note: On Unix-like platforms, environment variable LC_ALL is set too, so | |
that *external programs* see the given culture as well. | |
On Windows, this is *not* supported, unfortunately. | |
.PARAMETER Culture | |
The names / culture-info objects of one or more cultures to temporarily make | |
the current culture before the execution of the given script block. | |
* Use '' to represent the invariant culture. | |
* Use $null to represent the current culture. | |
.PARAMETER ScriptBlock | |
The script block to execute. | |
.EXAMPLE | |
Use-Culture $null, '', 'tr-TR' { 'ı'.Equals('I', 'CurrentCultureIgnoreCase') } | |
Contrasts the results of a culture-sensitive string comparison in the current, | |
invariant, and Turkish (tr-TR) cultures. | |
#> | |
param( | |
[Parameter(Mandatory)] [AllowNull()] [cultureinfo[]] $Culture, | |
[Parameter(Mandatory)] [scriptblock] $ScriptBlock | |
) | |
begin { | |
Set-StrictMode -Version 1 | |
if ($isMacOSOrLinux = $env:OS -ne 'Windows_NT') { $prevLC_ALL = $env:LC_ALL } | |
$prevCultures = [System.Threading.Thread]::CurrentThread.CurrentCulture, [System.Threading.Thread]::CurrentThread.CurrentUICulture | |
} | |
process { | |
try { | |
# $null means use the curren culture | |
# Note: '', by contrast, refers to the *invariant* culture. | |
if ($null -eq $Culture) { $Culture = [cultureinfo]::CurrentCulture } | |
foreach ($thisCulture in $Culture) { | |
# !! A culture-info object can be created from *any* string that is *formally* correct: | |
# !! See https://github.com/PowerShell/PowerShell/pull/7702#issuecomment-418932551 | |
# !! While LCID 4096 (0x1000) is assigned to non-predefined cultures, it is unfortunately shared by | |
# !! many *recently introduced* (W10) cultures - see https://msdn.microsoft.com/en-us/library/cc233982.aspx | |
# !! Therefore - short of searching through the output of [cultureinfo]::GetCultures('AllCultures') | |
# !! - we have no *reliable* way to detect if a culture is predefined - the best we can do is *warn*. | |
# !! Note that the LCID of the invariant culture is 127 | |
if ($thisCulture.LCID -eq 4096) { | |
if ($env:OS -eq 'Windows_NT' -or $thisCulture.Name -ne 'en-US-POSIX') { | |
# !! On Unix-like platforms 'en-US-POSIX' is the POSIX/C culture. | |
Write-Warning "Potentially non-predefined culture: $($thisCulture.DisplayName)" | |
} | |
} | |
if ($isMacOSOrLinux) { | |
$localeId = if ($thisCulture -eq [cultureinfo]::InvariantCulture) { 'POSIX' } else { $thisCulture.IetfLanguageTag -replace '-', '_' } | |
$env:LC_ALL = $localeId + ($env:LANG -replace '^[^.]+') | |
Write-Verbose "Overriding the locale for child processes: LC_ALL=$env:LC_ALL" | |
} | |
else { | |
Write-Verbose "WARNING: Cannot set culture for child processes on Windows." | |
} | |
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture = $thisCulture | |
Write-Verbose "Using culture '$([cultureinfo]::CurrentCulture.EnglishName)'." | |
& $ScriptBlock | |
} | |
} | |
finally { | |
if ($isMacOSOrLinux) { $env:LC_ALL = $prevLC_ALL } | |
[System.Threading.Thread]::CurrentThread.CurrentCulture = $prevCultures[0] | |
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $prevCultures[1] | |
} | |
} | |
} | |
# -- Generic code that handles the invocation if this script file is invoked directly. | |
if ($MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -eq '') { # Being dot-sourced | |
if ($MyInvocation.Line -eq '' -and $host.Name -eq 'Visual Studio Code Host') { # Support for debugging in VSCode | |
# If the current file is directly launched for debugging from the editor, | |
# use a custom invocation with arguments for debugging. | |
Write-Warning 'VSCode debugging: Invoking embedded function with custom arguments.' | |
& ([System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)) # Add arguments for debugging here. | |
} else { | |
# Nothing further to do: the function has been defined in the current scope for *later* use. | |
# However, we warn if arguments were unexpectedly passed or something is being piped. | |
if ($Args.Count -or $MyInvocation.ExpectingInput) { Write-Warning "Ignoring arguments and/or pipeline input due to being dot-sourced: $PSCommandPath" } | |
} | |
} else { # Script is invoked directly (by name/path directly or via &) | |
# Call the embedded function of the same name, relaying any arguments passed. | |
if ($MyInvocation.ExpectingInput) { # pipeline input present | |
$Input | & ([System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)) @Args | |
} else { # only arguments, if any | |
& ([System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)) @Args | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment