当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 使用 PowerShell 自定义 CSV 导入

作者:精品下载站 日期:2024-12-14 08:03:20 浏览:14 分类:玩电脑

使用 PowerShell 自定义 CSV 导入


[玩转系统] 使用 PowerShell 自定义 CSV 导入

我一直在寻找机会使用 PowerShell 来为我的工作增加价值。希望是你的。这是值得花时间和精力学习 PowerShell 的原因之一。除了开箱即用的命令之外,它还可以通过多种方式使用。一旦您了解了 PowerShell 语言并接受了管道中对象的范例,PowerShell 就会提供无限的可能性。就我而言,我想使用本机 Import-Csv 命令执行更多操作。我想保留原来的功能,但我希望它能做更多的事情。我是这样做的。即使您不需要最终结果,我也希望您能够关注 PowerShell 脚本编写技术和概念。这些是您可以应用到您自己的工作中的项目。

复制命令

我首先使用 PSScriptTools 模块中的 Copy-Command 函数。我知道我想添加一些参数和功能。我最初想创建一个代理函数并用我的自定义版本替换 Import-CSV。但最终决定创建一个“包装”函数。这种类型的函数共享传递给它的目标命令的大部分(如果不是全部)参数。 Copy-Command 功能将参数从原始命令复制到新命令。

调整参数

一旦我有了新的参数,我就开始调整。首先,我去掉了 LiteralPath 参数并将其与 Path 参数组合起来。本质上,我将每条路径都视为字面路径。我还添加了自定义参数验证。

[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,

验证脚本验证路径以及它是否是文件系统对象。

[玩转系统] 使用 PowerShell 自定义 CSV 导入

我还知道在导入 CSV 数据时我想添加两个可选功能。首先,我想捕获源文件。我希望它成为自定义输出中的另一个属性。

[Parameter(HelpMessage = "Add a custom property to reflect the import source file.")][switch]$IncludeSource,

我还希望能够定义类型名。从 CSV 文件导入数据时,PowerShell 会将通用自定义对象写入管道。但如果我给它一个类型名称,并使用自定义格式文件(你知道我这样做),我可以改善导入体验。我们稍后会谈到这一点。

System.Collections.Generic.List[]

这两个操作都要求我在使用本机 Import-Csv 命令导入数据后修改数据。请记住,我正在编写一个包装函数,它提供相同的功能以及我的添加内容。因为我需要在导入之后和将对象写入管道之前修改对象,所以我需要一个临时位置来保存它们。从技术上讲,数组是可行的。我可以初始化一个空数组,然后将导入的项目添加到该数组中。但是,从技术上讲,当您向数组添加项目时,PowerShell 会销毁并重新创建该数组。对于小型数据集,这并不是那么重要。但我喜欢尽可能提高效率,所以我一直在使用通用列表。

您可以像这样定义一个新的列表对象:

$in = [System.Collections.Generic.List[object]]::New()

您可以指定列表中的对象类型,例如字符串,或者在我的例子中为通用“对象”。在我的函数中,我利用了Using 语句。在函数之前我有这行代码:

Using Namespace System.Collections.Generic

在我的函数中,这简化了创建列表对象所需的代码。

$in = [List[object]]::New()

您可以使用 Add() 方法将单个对象添加到列表中,也可以使用 AddRange() 一次将多个对象添加到列表中。我没有发现两者之间有太大区别,因此为了简单起见,我将每个导入的项目添加到列表中。

Microsoft.PowerShell.Utility\Import-Csv @PSBoundParameters | ForEach-Object { $in.Add($_) }

请注意,我将 PSBoundParameters 分配给本机命令。我使用的是完全限定的命令名称。这允许我调用我的函数 Import-Csv ,该函数在命令提示符下优先。顺便说一句,我不是。我的函数称为 Import-CsvCustom,但我想包含此函数,以防您决定重命名并演示脚本概念。

自定义对象

有了列表中的所有导入数据,我现在可以修改对象。如果用户指定包含源路径,我将向列表中的对象添加注释属性。

if ($IncludeSource) {
    Write-Verbose "[PROCESS] Adding CSVSource property"
    $in | Add-Member -MemberType NoteProperty -Name CSVSource -Value $cPath -Force
}

[玩转系统] 使用 PowerShell 自定义 CSV 导入

我正在考虑如何将其设为隐藏属性并让我指定属性名称。我的任何 CSV 文件中不太可能有名为 CSVSource 的列。但总的来说,我喜欢有灵活性。

正如我提到的,当您导入 CSV 文件时,您将获得通用自定义对象。

[玩转系统] 使用 PowerShell 自定义 CSV 导入

正如您在上图中看到的,我获得了所有对象和属性的列表。但是,如果我有一个已加载到会话中的自定义格式文件怎么办?

Update-FormatData C:\scripts\bedrock.format.ps1xml

现在,我可以导入数据并分配类型名称。

[玩转系统] 使用 PowerShell 自定义 CSV 导入

导入-CsvCustom

想亲自尝试一下吗?

#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-CsvCustom

将代码保存到一个文件中,您可以在 PowerShell 会话中点源该文件。然后导入 CSV 文件并尝试一下。该函数应该在 Windows PowerShell 和 PowerShell 7.x 中工作。该函数包括详细输出,并希望有大量内部文档来帮助您理解我在做什么。

我希望您觉得这很有用,或者至少提供了信息。也许您也会开始考虑如何使用 PowerShell 来做更多事情。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯