Skip to content

Instantly share code, notes, and snippets.

@mklement0
Created September 9, 2020 23:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mklement0/d80c80c5ee9b627cbecfccc4ce073c3b to your computer and use it in GitHub Desktop.
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
<#
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