从多个XML文件中的元素获取数据,以使用Powershell输出到另一个单个XML文件

时间:2020-12-16 09:44:05

I'll begin by confessing that I'm a Powershell (and coding) noob. I've stumbled my way through a few scripts, but I make no claims to anything even approaching competence. I'm hopeful that some more experienced folks can set me on the right track.

我首先承认我是一个Powershell(和编码)菜鸟。我偶然发现了一些脚本,但我对任何能力都没有任何要求。我希望一些更有经验的人能让我走上正轨。

I'm trying to pull specific element-data from multiple XML files, which will be used to populate another XML file. The files from which I'm pulling the data are invoices, and I'd like to grab the invoice number and timestamp and drop those values into a manifest. The manifest structure is as follows

我正在尝试从多个XML文件中提取特定的元素数据,这些文件将用于填充另一个XML文件。我从中提取数据的文件是发票,我想获取发票号和时间戳并将这些值放入清单中。清单结构如下

<?xml version="1.0" encoding="utf-8"?>
<Manifest>
    <Invoice>
        <InvoiceID></InvoiceID>
        <Timestamp></Timestamp>
    </Invoice>
</Manifest>

The XMLs from which I am pulling are in a sub-directory of the directory in which the manifest will be saved. For the sake of simplicity, the element names within the invoices are identical to the corresponding elements within the manifest. The folder structure for the manifest is "C:\Projects\Powershell\Manifest\Manifest.xml" and for the invoices it is "C:\Projects\Powershell\Manifest\Invoices\*.xml".

我从中提取的XML位于将保存清单的目录的子目录中。为简单起见,发票中的元素名称与清单中的相应元素相同。清单的文件夹结构是“C:\ Projects \ Powershell \ Manifest \ Manifest.xml”,对于发票,它是“C:\ Projects \ Powershell \ Manifest \ Invoices \ * .xml”。

With the following code I am able to grab the data from the elements "InvoiceID" and "Timestamp" of only the first XML in the sub-directory "\Invoices". The code does, however, create one entry for each Invoice file; it just fills each element with the value taken from the first file. (So, for example, if I have three Invoice XML files in the "\Invoices" directory, I get results of: three instances of the <Invoice> complex element, each populated with the InvoiceID and Timestamp found in the first file. So it's counting the files and outputting a corresponding number of elements, it just isn't getting data from any but the first.)

使用以下代码,我能够从子目录“\ Invoices”中的第一个XML元素“InvoiceID”和“Timestamp”中获取数据。但是,代码会为每个Invoice文件创建一个条目;它只是用第一个文件中的值填充每个元素。 (例如,如果我在“\ Invoices”目录中有三个Invoice XML文件,我会得到以下结果: 复杂元素的三个实例,每个实例都填充了第一个文件中的InvoiceID和Timestamp。它正在计算文件并输出相应数量的元素,它只是从第一个获取数据。)

Here is the code:

这是代码:

$files = Get-ChildItem "C:\Projects\Powershell\Manifest\Invoices\*.xml"

$xmlData = @"
    <Invoice>
        <InvoiceId>$InvID</InvoiceId>
        <Timestamp>$Timestamp</Timestamp>
    </Invoice>
"@
$Manifest = "C:\Projects\Powershell\Manifest\Manifest.xml"

ForEach ($file in $files) {
    $xmldoc = [xml](Get-Content $file)
    $InvID = $xmldoc.Manifest.Invoice.InvoiceID
    $Timestamp = $xmldoc.Manifest.Invoice.Timestamp
    ForEach ($xml in $xmldoc)
{
    Add-Content $Manifest $xmlData
}}

I can deal with properly formatting the closing tag of the output file once I have this piece figured out.

一旦我弄清楚这件事,我可以处理正确格式化输出文件的结束标记。

I know I must be looping incorrectly, but after reading up on this until my brain hurts, I've finally resorted to asking the question. What obvious thing am I missing/messing up?

我知道我必须错误地循环,但在阅读完之后直到我的大脑受伤,我终于求助于提问。什么明显的东西我错过/弄乱了?

1 个解决方案

#1


2  

String interpolation (expansion) in "..." and @"<newline>...<newline>"@ strings happens instantly, with the values that the referenced variables contain at that time getting used.
As a result, the same string - whose value was determined before the loop - is output in each iteration of your foreach loop.

“...”和@“ ... ”@字符串中的字符串插值(扩展)立即发生,其中引用的变量包含的值在此时被使用。结果,在foreach循环的每次迭代中输出相同的字符串 - 其值在循环之前确定。

Your use case calls for a templating approach, where string interpolation is deferred, to be invoked on demand with the then-current variable values, using $ExecutionContext.InvokeCommand.ExpandString():

您的用例需要使用$ ExecutionContext.InvokeCommand.ExpandString()调用模板方法,其中字符串插值是延迟的,可以使用当前变量值按需调用:

# Define the *template* string as a *literal* - with *single* quotes.
$xmlData = @'
    <Invoice>
        <InvoiceId>$InvID</InvoiceId>
        <Timestamp>$Timestamp</Timestamp>
    </Invoice>
'@

 # ...
 # ForEach ($file in $files) { ...
   # Perform interpolation *on demand* with $ExecutionContext.InvokeCommand.ExpandString()
   Add-Content $Manifest -Value $ExecutionContext.InvokeCommand.ExpandString($xmlData)
 # }

A simple example:

一个简单的例子:

# Define a template string, *single-quoted*, with *literal contents*:
#  - '$InvID' is simply literally part of the string, not a variable reference (yet).
#  - Ditto for $((Get-Date).TimeOfDay)
$strTempl = 'Invoice ID $InvID extracted at $((Get-Date).TimeOfDay).'

# Echo the template string as-is - unexpanded - ...
$strTempl

# ... and expand it on demand
$InvID = 1
$ExecutionContext.InvokeCommand.ExpandString($strTempl)

# ... and again, after assigning a different value to $InvID
$InvID = 2
$ExecutionContext.InvokeCommand.ExpandString($strTempl)

The above yields something like:

以上结果如下:

Invoice ID $InvID extracted at $((Get-Date).TimeOfDay).  # template literal
Invoice ID 1 extracted at 11:38:12.2719300.              # first on-demand expansion
Invoice ID 2 extracted at 11:38:12.2766010.              # second on-demand expnsion

#1


2  

String interpolation (expansion) in "..." and @"<newline>...<newline>"@ strings happens instantly, with the values that the referenced variables contain at that time getting used.
As a result, the same string - whose value was determined before the loop - is output in each iteration of your foreach loop.

“...”和@“ ... ”@字符串中的字符串插值(扩展)立即发生,其中引用的变量包含的值在此时被使用。结果,在foreach循环的每次迭代中输出相同的字符串 - 其值在循环之前确定。

Your use case calls for a templating approach, where string interpolation is deferred, to be invoked on demand with the then-current variable values, using $ExecutionContext.InvokeCommand.ExpandString():

您的用例需要使用$ ExecutionContext.InvokeCommand.ExpandString()调用模板方法,其中字符串插值是延迟的,可以使用当前变量值按需调用:

# Define the *template* string as a *literal* - with *single* quotes.
$xmlData = @'
    <Invoice>
        <InvoiceId>$InvID</InvoiceId>
        <Timestamp>$Timestamp</Timestamp>
    </Invoice>
'@

 # ...
 # ForEach ($file in $files) { ...
   # Perform interpolation *on demand* with $ExecutionContext.InvokeCommand.ExpandString()
   Add-Content $Manifest -Value $ExecutionContext.InvokeCommand.ExpandString($xmlData)
 # }

A simple example:

一个简单的例子:

# Define a template string, *single-quoted*, with *literal contents*:
#  - '$InvID' is simply literally part of the string, not a variable reference (yet).
#  - Ditto for $((Get-Date).TimeOfDay)
$strTempl = 'Invoice ID $InvID extracted at $((Get-Date).TimeOfDay).'

# Echo the template string as-is - unexpanded - ...
$strTempl

# ... and expand it on demand
$InvID = 1
$ExecutionContext.InvokeCommand.ExpandString($strTempl)

# ... and again, after assigning a different value to $InvID
$InvID = 2
$ExecutionContext.InvokeCommand.ExpandString($strTempl)

The above yields something like:

以上结果如下:

Invoice ID $InvID extracted at $((Get-Date).TimeOfDay).  # template literal
Invoice ID 1 extracted at 11:38:12.2719300.              # first on-demand expansion
Invoice ID 2 extracted at 11:38:12.2766010.              # second on-demand expnsion