ConvertTo-CSV -UseCulture игнорирует культуру текущего потока
Вопрос
Можно ли заставить PowerShell экспортировать в CSV во французском формате при запуске в сеансе Windows с en-GB
культура?
Больше информации
Я надеюсь экспортировать некоторые данные в CSV, используя французские правила культуры (то есть разделитель CSV установлен на точку с запятой, но также с числами, использующими запятые для десятичных разрядов, и другие различия в культурном форматировании; так что просто используя -Delimiter
параметр не достаточен).
Я пришел с приведенным ниже кодом (на основе /questions/14463120/powershell-izmenenie-kulturyi-tekuschej-sessii/14463136#14463136)
function Set-Culture
{
[CmdletBinding(DefaultParameterSetName='ByCode')]
param (
[Parameter(Mandatory,ParameterSetName='ByCode',Position=1)]
[string] $CultureCode
,
[Parameter(Mandatory,ParameterSetName='ByCulture',Position=1)]
[System.Globalization.CultureInfo] $Culture
)
begin {
[System.Globalization.CultureInfo] $Culture = [System.Globalization.CultureInfo]::GetCultureInfo($CultureCode)
}
process {
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $Culture
[System.Threading.Thread]::CurrentThread.CurrentCulture = $Culture
}
}
function Invoke-CommandInCulture {
[CmdletBinding()]
param (
[Parameter(Mandatory,ParameterSetName='ByCode',Position=1)]
[string]$CultureCode
,
[Parameter(Mandatory,Position=2)]
[ScriptBlock]$Code
)
process {
$OriginalCulture = Get-Culture
try
{
Set-Culture $CultureCode
Write-Verbose (Get-Culture) #this always returns en-GB
Invoke-Command -ScriptBlock $Code
}
finally
{
Set-Culture $OriginalCulture
}
}
}
Следующий код подразумевает, что этот метод работает:
Invoke-CommandInCulture -CultureCode 'fr' -Code {
[System.Threading.Thread]::CurrentThread.CurrentUICulture
[System.Threading.Thread]::CurrentThread.CurrentCulture
} #shows that the command's thread's culture is French
Invoke-CommandInCulture -CultureCode 'fr' -Code {
get-date
} #returns the current date in French
Однако PowerShell имеет свое представление о том, что происходит
Invoke-CommandInCulture -CultureCode 'fr' -Code {
get-culture
"PSCulture: $PSCulture"
"PSUICulture: $PSUICulture"
} #returns my default (en-GB) culture; not the thread's culture
И это влияет на логику для преобразования в CSV:
Invoke-CommandInCulture -CultureCode 'fr' -Code {
get-process | ConvertTo-CSV -UseCulture
} #again, uses my default culture's formatting rules; not the FR ones
1 ответ
Обходной путь № 1: Пользовательская функция для преобразования значений в строки в заданной культуре
Вот обходное решение; преобразование каждого поля в строку с использованием заданной культуры, а затем преобразование значений строки в CSV:
function ConvertTo-SpecifiedCulture {
[CmdletBinding()]
param (
[Parameter(Mandatory,ValueFromPipeline)]
[PSObject]$InputObject
,
[Parameter(Mandatory)]
[string]$CultureCode
,
[Parameter(ParameterSetName='DefaultParameter', Position=0)]
[System.Object[]]$Property
)
begin {
[System.Globalization.CultureInfo] $Culture = [System.Globalization.CultureInfo]::GetCultureInfo($CultureCode)
}
process {
if($Property -eq $null) {$Property = $InputObject.PSStandardMembers.DefaultDisplayPropertySet.ReferencedPropertyNames}
$Result = new-object -TypeName PSObject -Property @{}
$Property | %{
$Result | Add-Member -MemberType NoteProperty -Name $_ -Value ($InputObject."$_").ToString($Culture)
}
$Result
}
}
Get-Process | select -first 2 | ConvertTo-SpecifiedCulture -CultureCode 'fr' | ConvertTo-CSV -Delimiter ';'
Обходной путь № 2: Переопределить текущую культуру для соответствия целевой культуре
Другой вариант - изменить настройки текущей культуры, чтобы они соответствовали настройкам требуемой культуры. Это кажется более хакерским; хотя в зависимости от сценария может получиться чище / практичнее, чем выше.
Например, чтобы использовать числовой формат FR, мы просто обновляем числовой формат текущей культуры, чтобы соответствовать FR:
$(Get-Culture).NumberFormat = ([System.Globalization.CultureInfo]'FR').NumberFormat
... и мы можем сделать то же самое для остальных (устанавливаемых) свойств:
function Set-CurrentCulture {
[CmdletBinding()]
param (
[string]$CultureCode
)
begin {
$Global:FakedCurrentCulture = $CultureCode #in case we need a reference to the current culture's name
[System.Globalization.CultureInfo]$NewCulture = [System.Globalization.CultureInfo]::GetCultureInfo($CultureCode)
[System.Globalization.CultureInfo]$ReferenceToCurrentCulture = Get-Culture
Write-Verbose "Switching Defintion to $($NewCulture.Name)"
}
process {
#NB: At time of writing, the only settable properties are NumberFormatInfo & DateTimeFormatInfo
$ReferenceToCurrentCulture.psobject.properties | ?{$_.isSettable} | %{
$propertyName = $_.Name
write-verbose "Setting property $propertyName"
write-verbose "- from: $($ReferenceToCurrentCulture."$propertyName")"
write-verbose "- to: $($NewCulture."$propertyName")"
$ReferenceToCurrentCulture."$propertyName" = $NewCulture."$propertyName"
}
#ListSeparator
$ReferenceToCurrentCulture.TextInfo.psobject.properties | ?{$_.isSettable} | %{
$propertyName = $_.Name
write-verbose "Setting property TextInfo.$propertyName"
write-verbose "- from: $($ReferenceToCurrentCulture.TextInfo."$propertyName")"
write-verbose "- to: $($NewCulture.TextInfo."$propertyName")"
$ReferenceToCurrentCulture.TextInfo."$propertyName" = $NewCulture."$propertyName"
}
#for some reason this errors
#Localized, TwoDigitYearMax
<#
$ReferenceToCurrentCulture.Calendar.psobject.properties | ?{$_.isSettable} | %{
$propertyName = $_.Name
write-verbose "Setting property Calendar.$propertyName"
write-verbose "- from: $($ReferenceToCurrentCulture.Calendar."$propertyName")"
write-verbose "- to: $($NewCulture.Calendar."$propertyName")"
$ReferenceToCurrentCulture.Calendar."$propertyName" = $NewCulture."$propertyName"
}
#>
}
}
function Reset-CurrentCulture {
[CmdletBinding()]
param ()
process {
Set-CurrentCulture -CultureCode ((get-culture).Name)
}
}
function Test-It {
[CmdletBinding()]
param ()
begin {
write-verbose "Current Culture: $Global:FakedCurrentCulture"
}
process {
1..5 | %{
New-Object -TypeName PSObject -Property @{
Integer = $_
String = "Hello $_"
Numeric = 2.139 * $_
Money = (2.139 * $_).ToString('c')
Date = (Get-Date).AddDays($_)
}
} | ConvertTo-Csv -NoTypeInformation
}
}
Set-CurrentCulture 'fr' -Verbose
Test-It
Set-CurrentCulture 'en-GB' -Verbose
Test-It
Set-CurrentCulture 'en-US' -Verbose
Test-It
Set-CurrentCulture 'ar-DZ' -Verbose
Test-It
Reset-CurrentCulture -Verbose
Test-It
Мы могли бы пойти дальше и посмотреть на перезапись свойств только для чтения (это возможно: https://learn-powershell.net/2016/06/27/quick-hits-writing-to-a-read-only-property/)... но это уже кажется очень неприятным; поэтому я не пойду туда, так как выше было достаточно для моих нужд.