如何在PowerShell中向现有CSV行添加列?

时间:2018-04-08 21:51:20

标签: powershell csv

我正在尝试在我的脚本中编写一个简单的使用记录器,它将存储有关用户打开脚本的时间,使用脚本和用户名完成的信息。

我收集前两个数据的记录器的第一部分工作正常,并为CSV文件添加了两个必要的值列。然而,当我运行记录器的第二部分时,它不会在我现有的CSV文件中添加新列。

#Code I will add at the very beginning of my script
$FileNameDate = Get-Date -Format "MMM_yyyy"
$FilePath = "C:\Users\Username\Desktop\Script\Logs\${FileNameDate}_MonthlyLog.csv"
$TimeStamp = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
$UserName = [string]($env:UserName)
$LogArray = @()
$LogArrayDetails = @{
    Username = $UserName
    StartDate = $TimeStamp
}
$LogArray += New-Object PSObject -Property $LogArrayDetails | Export-Csv $FilePath -Notypeinformation -Append
#Code I will add at the very end of my script
$logArrayFinishDetails = @{FinishDate = $TimeStamp}
$LogCsv = Import-Csv $FilePath | Select Username, StartDate, @{$LogArrayFinishDetails} | Export-Csv $FilePath -NoTypeInformation -Append

当脚本关闭时,CSV文件应该如下所示:

Username    StartDate               FinishDate
anyplane    08/Apr/2018 23:47:55    08/Apr/2018 23:48:55

但它看起来像这样:

StartDate               Username                
08/Apr/2018 23:47:55    anyplane

另一个奇怪的事情是它将StartDate放在第一位,而我在$ LogArrayDetails中明确指出Username是第一位的。

2 个答案:

答案 0 :(得分:3)

由于您的CSV已经具有结构(即已定义的标头),因此PowerShell会在追加时遵循此规则,并且不会添加其他列。在[{1}} help

的摘录中解释了这种情况
  

当您向Export-CSV提交多个对象时,Export-CSV会进行组织   该文件基于您提交的第一个对象的属性。   如果剩余的对象没有指定的属性之一,   该对象的属性值为null,由两个表示   连续逗号。 如果剩余的对象有额外的   属性,这些属性值不包含在文件中

您可以在原始文件中包含Export-Csv属性(即使它是空的),但最好的选择可能是在最后将输出导出到另一个CSV,也许在导入后删除原始文件然后用附加数据重新创建它。事实上,只需删除FinishDate即可获得您想要的结果。

答案 1 :(得分:3)

假设您只想记录最近一次运行 [如果您想记录多次运行,请参见底部] (PSv3 +) :

# Log start of execution.
[pscustomobject] @{ Username = $env:USERNAME; StartDate = $TimeStamp } |
  Export-Csv -Notypeinformation $FilePath

# Perform script actions...

# Log end of execution.
(Import-Csv $FilePath) | 
  Select-Object *, @{ n='FinishDate'; e={ (Get-Date).toString("dd/MMM/yyyy HH:mm:ss") } } |
    Export-Csv -Notypeinformation $FilePath
  • boxdog's helpful answer所述,-AppendExport-Csv一起使用不会添加其他列。

  • 但是,由于您似乎正在尝试重写整个文件,因此无需使用
    完全-Append

    • 为了确保在您尝试使用Export-Csv进行替换之前,已完全读取了旧版本的文件但请务必将Import-Csv $FilePath来电置于(...) 对于1行文件(例如在这种情况下),这不是严格必要的,但是为这种重写形成一个好习惯;请注意,这种方法通常有些脆弱,因为在重写文件时可能会出错,导致潜在的数据丢失。

    • @{ n='FinishDate'; e={ (Get-Date).toString("dd/MMM/yyyy HH:mm:ss") }是附加到预先存在的列(*

    • calculated property/column示例
  

另一个奇怪的事情是它将StartDate放在第一位,而我在$ LogArrayDetails中明确声明用户名是第一位的。

您已使用哈希表(@{ ... })声明输出CSV的列,但无法保证枚举哈希表条目的顺序。

在PSv3 +中,您可以使用有序哈希表([ordered] @{ ... })来实现可预测的枚举,如果将哈希表转换为a ,则 自定义对象通过转换为[pscustomobject],如上所示。

如果您确实要追加到现有文件,可以使用以下内容,但请注意:

  • 这种方法不能很好地扩展,因为每次都会将整个日志文件读入内存(并转换为对象),但将条目限制为一个月的价值应该没问题。

  • 如上所述,这种方法很脆弱,因为重写文件时可能会出错;考虑简单地每次执行写入 2 行,这样就可以逐行追加到文件中。

  • 没有并发管理,所以假设一次只运行一个脚本实例。

$FilePath = './t.csv'
$TimeStamp = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
$env:USERNAME = $env:USER

# Log start of execution. Note the empty 'FinishDate' property
# to ensure all rows ultimately have the same column structure.
[pscustomobject] @{ Username = $env:USERNAME; StartDate = $TimeStamp; FinishDate = '' } |
  Export-Csv -Notypeinformation -Append $FilePath

# Perform script actions...

# Log end of execution:
# Read the entire existing file...
$logRows = Import-Csv $FilePath
# ... update the last row's .FinishDate property
$logRows[-1].FinishDate = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
# ... and rewrite the entire file, keeping only the last 30 entries
$logRows[-30..-1] | Export-Csv -Notypeinformation $FilePath