PowerShell ile Özel CSV İçe Aktarma
Her zaman PowerShell'i işime değer katacak şekilde kullanma fırsatlarını arıyorum. Ve umarım senindir. PowerShell'i öğrenmek için zaman ve çabaya değer olmasının nedenlerinden biri de budur. Kullanıma hazır komutların ötesinde pek çok şekilde kullanılabilir. PowerShell dilini anladığınızda ve üretim hattındaki nesnelerin paradigmasını benimsediğinizde PowerShell sınırsız olanaklar sunar. Benim durumumda yerel Import-Csv komutuyla daha fazlasını yapmak istedim. Orijinal işlevselliği korumak istedim ama daha fazlasını yapmasını istedim. İşte bunu nasıl yaptım. Nihai sonuca ihtiyacınız olmasa bile PowerShell betik oluşturma tekniklerine ve kavramlarına dikkat edeceğinizi umuyorum. Bunlar kendi işinize uygulayabileceğiniz öğelerdir.
Kopyala-Komut
PSScriptTools modülündeki Copy-Command işlevini kullanarak başladım. Bazı parametreler ve işlevler eklemek istediğimi biliyordum. Başlangıçta bir proxy işlevi oluşturup Import-CSV'yi özel sürümümle değiştireceğimi düşündüm. Ancak sonuçta bir "sarmalayıcı" işlevi oluşturmaya karar verdim. Bu tür bir işlev, kendisine iletilen bir hedef komutun parametrelerinin hepsini olmasa da çoğunu paylaşır. Copy-Command işlevi, parametreleri orijinal komuttan yeni komutunuza kopyalar.
Parametrelerin Ayarlanması
Yeni parametreleri aldıktan sonra ince ayar yapmaya başladım. Öncelikle LiteralPath parametresinden kurtulup Path parametresi ile birleştirdim. Aslında her yolu gerçek bir yol olarak ele alıyorum. Ayrıca özel parametre doğrulamayı da ekledim.
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = "The path to the CSV file. Every path is treated as a literal path."
)]
[ValidateNotNullOrEmpty()]
#Validate file exists
[ValidateScript({
If ((Test-Path $_) -AND ((Get-Item $_).PSProvider.Name -eq 'FileSystem')) {
$True
}
else {
Write-Warning "Failed to verify $($_.ToUpper()) or it is not a file system object."
Throw "Failed to validate the path parameter."
$False
}
})]
[Alias("PSPath")]
[string[]]$Path,Doğrulama komut dosyası yolu ve bunun bir dosya sistemi nesnesi olduğunu doğrular.
CSV verilerini içe aktarırken eklemek istediğim iki isteğe bağlı özellik olduğunu da biliyordum. İlk önce kaynak dosyayı yakalamak istedim. Özel çıktıda başka bir özellik olmasını istedim.
[Parameter(HelpMessage = "Add a custom property to reflect the import source file.")][switch]$IncludeSource,Ayrıca bir yazı tipi adı tanımlama yeteneğini de istedim. Bir CSV dosyasından verileri içeri aktardığınızda PowerShell, ardışık düzene genel bir özel nesne yazar. Ancak ona bir yazı adı verirsem ve özel formatlı dosyalar kullanırsam (bunu yaptığımı biliyorsunuz), içe aktarma deneyimini geliştirebilirim. Birazdan buna ulaşacağız.
System.Collections.Generic.List[]
Bu eylemlerin her ikisi de, yerel Import-Csv komutuyla içe aktarıldıktan sonra verileri değiştirmemi gerektirecektir. Unutmayın, aynı işlevselliği ve eklemelerimi sunan bir sarmalayıcı işlevi yazıyorum. Nesneleri içe aktarmadan sonra ve bunları boru hattına yazmadan önce değiştirmem gerektiğinden, onları tutacak geçici bir yere ihtiyacım var. Teknik olarak bir dizi işe yarayacaktır. Boş bir dizi başlatabilir ve ardından içe aktarılan öğeleri diziye ekleyebilirim. Ancak teknik olarak bir diziye bir öğe eklediğinizde PowerShell diziyi yok ediyor ve yeniden oluşturuyor. Küçük veri kümeleri için bu o kadar da kritik değildir. Ancak elimden geldiğince verimli çalışmayı seviyorum, bu yüzden genel listeler kullanıyorum.
Bunun gibi yeni bir liste nesnesi tanımlayabilirsiniz:
$in = [System.Collections.Generic.List[object]]::New()Listede ne tür bir nesnenin (örneğin dize) veya benim durumumda genel 'nesne' olduğunu belirtebilirsiniz. Fonksiyonumda, Use ifadesinin avantajlarından yararlanıyorum. Fonksiyondan önce şu kod satırına sahibim:
Using Namespace System.Collections.GenericBenim fonksiyonumda bu, liste nesnesini oluşturmak için gereken kodu basitleştirir.
$in = [List[object]]::New()Listeye Add() yöntemiyle tek bir nesne ekleyebilir veya AddRange() ile birden çok nesneyi aynı anda ekleyebilirsiniz. İkisi arasında pek bir fark bulamadım, bu yüzden basit tutmak için içe aktarılan her öğeyi listeye ekleyeceğim.
Microsoft.PowerShell.Utility\Import-Csv @PSBoundParameters | ForEach-Object { $in.Add($_) }PSBoundParameters'ı yerel komuta aktardığıma dikkat edin. Tam nitelikli komut adını kullanıyorum. Bu, komut isteminde öncelikli olan Import-Csv işlevimi çağırmamı sağlıyor. Bu arada ben değilim. Benim işlevimin adı Import-CsvCustom, ancak yeniden adlandırmaya ve bir komut dosyası oluşturma konseptini göstermeye karar vermeniz durumunda bunu dahil etmek istedim.
Nesneleri Özelleştirme
Listedeki tüm içe aktarılan verilerle artık nesneleri değiştirebilirim. Kullanıcı tarafından belirtilen kaynak yolunun eklenmesi durumunda listedeki nesnelere not özelliği ekleyeceğim.
if ($IncludeSource) {
Write-Verbose "[PROCESS] Adding CSVSource property"
$in | Add-Member -MemberType NoteProperty -Name CSVSource -Value $cPath -Force
}
Bunu nasıl gizli bir mülk haline getireceğim ve mülk adını belirlememe izin vereceğim konusunda bazı fikirler üzerinde çalışıyorum. CSV dosyalarımdan herhangi birinde CSVSource adında bir sütunun olması pek olası değil. Ama kural olarak esnek olmayı severim.
Bahsettiğim gibi, bir CSV dosyasını içe aktardığınızda genel özel nesneler elde edersiniz.
Ve önceki resimde görebileceğiniz gibi tüm nesnelerin ve özelliklerin bir listesini alıyorum. Peki ya oturumuma yüklediğim özel formatlı bir dosyam varsa?
Update-FormatData C:\scripts\bedrock.format.ps1xmlArtık verileri içe aktarabilir ve bir tür adı atayabilirim.
İçe Aktarma-CsvCustom
Kendiniz denemek ister misiniz?
#requires -version 5.1
<#
This is a copy of:
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Import-Csv 3.1.0.0 Microsoft.PowerShell.Utility
Created: 17 May 2021
Author : Jeff Hicks
Learn more about PowerShell: https://jdhitsolutions.com/blog/essential-powershell-resources/
#>
<#
I am using a namespace to make defining a List[] object easier later
in the script.
#>
Using Namespace System.Collections.Generic
Function Import-CSVCustom {
#TODO - Add comment-based help
[CmdletBinding(DefaultParameterSetName = 'Delimiter')]
Param(
[Parameter(ParameterSetName = 'Delimiter', Position = 1)]
[ValidateNotNull()]
[char]$Delimiter,
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = "The path to the CSV file. Every path is treated as a literal path."
)]
[ValidateNotNullOrEmpty()]
#Validate file exists
[ValidateScript({
If ((Test-Path $_) -AND ((Get-Item $_).PSProvider.Name -eq 'FileSystem')) {
$True
}
else {
Write-Warning "Failed to verify $($_.ToUpper()) or it is not a file system object."
Throw "Failed to validate the path parameter."
$False
}
})]
[Alias("PSPath")]
[string[]]$Path,
[Parameter(ParameterSetName = 'UseCulture', Mandatory)]
[ValidateNotNull()]
[switch]$UseCulture,
[ValidateNotNullOrEmpty()]
[string[]]$Header,
[ValidateSet('Unicode', 'UTF7', 'UTF8', 'ASCII', 'UTF32', 'BigEndianUnicode', 'Default', 'OEM')]
[string]$Encoding,
[Parameter(HelpMessage = "Add a custom property to reflect the import source file.")]
[switch]$IncludeSource,
[Parameter(HelpMessage = "Insert an optional custom type name.")]
[ValidateNotNullOrEmpty()]
[string]$PSTypeName
)
Begin {
Write-Verbose "[BEGIN ] Starting $($MyInvocation.Mycommand)"
Write-Verbose "[BEGIN ] Using parameter set $($PSCmdlet.ParameterSetName)"
Write-Verbose ($PSBoundParameters | Out-String)
#remove parameters that don't belong to the native Import-Csv command
if ($PSBoundParameters.ContainsKey("IncludeSource")) {
[void]$PSBoundParameters.Remove("IncludeSource")
}
if ($PSBoundParameters.ContainsKey("PSTypeName")) {
[void]$PSBoundParameters.Remove("PSTypeName")
}
} #begin
Process {
<#
Initialize a generic list to hold each imported object so it can be
processed for CSVSource and/or typename
#>
$in = [List[object]]::New()
#convert the path value to a complete filesystem path
$cPath = Convert-Path -Path $Path
#update the value of the PSBoundparameter
$PSBoundParameters["Path"] = $cPath
Write-Verbose "[PROCESS] Importing from $cPath"
<#
Add each imported item to the collection.
It is theoretically possible to have a CSV file of 1 object, so
instead of testing to determine whether to use Add() or AddRange(),
I'll simply Add each item.
I'm using the fully qualified cmdlet name in case I want this function
to become my Import-Csv command.
#>
Microsoft.PowerShell.Utility\Import-Csv @PSBoundParameters | ForEach-Object { $in.Add($_) }
Write-Verbose "[PROCESS] Post-processing $($in.count) objects"
if ($IncludeSource) {
Write-Verbose "[PROCESS] Adding CSVSource property"
$in | Add-Member -MemberType NoteProperty -Name CSVSource -Value $cPath -Force
}
if ($PSTypeName) {
Write-Verbose "[PROCESS] Adding PSTypename $PSTypeName"
$($in).foreach({ $_.psobject.typenames.insert(0, $PSTypeName)})
}
#write the results to the pipeline
$in
} #process
End {
Write-Verbose "[END ] Ending $($MyInvocation.Mycommand)"
} #end
} #end Import-CsvCustomKodu, PowerShell oturumunuzda kaynak olarak kullanabileceğiniz bir dosyaya kaydedin. Daha sonra CSV dosyalarını içe aktarın ve deneyin. İşlev, Windows PowerShell ve PowerShell 7.x'te çalışmalıdır. İşlev, Verbose çıktısını ve ne yaptığımı anlamanıza yardımcı olacak çok sayıda dahili belgeyi içerir.
Umarım bunu yararlı ya da en azından bilgilendirici bulursunuz. Belki siz de daha fazlasını yapmak için PowerShell'i kullanmanın yollarını düşünmeye başlayacaksınız.




