dynamic hastable key and value

I need to extract the “Last Author” field from several thousand Word files and have put this code together to get the values needed, but am having a difficult time getting the output to CSV. For a single file, it works for outputting the content on the screen.

I’ve tried Out-File and that gets me a single column of the $VarPropValue content.

I work with Custom Objects frequently, but am stuck on getting this converted to Key = $Var, Value = $VarPropValue, such that the key value is what-ever the content at the time the $Var variable passed through the foreach loop.

Using fixed key names, I often create dynamic value “values”, but an not sure how to create a loop with unique key values each time.

Key | Value
Title | My Title
Author | Someone Else
etc…

Title: My Title
Subject:
Author: Someone Else
Keywords:
Comments:
Template: Notebook Template
Last author: Harrington, Grant
Creation date: 01/08/2018 17:43:00
Last save time: 01/08/2018 17:43:00
Number of pages: 3
Company: My Company


$wordApplication = New-Object -ComObject word.application

$document = $wordApplication.documents.open("C:\Users\Grant.Harrington\Desktop\TEMPWORD\Test\2018-01-08_22-43-00.DOCX");

$binding = "System.Reflection.BindingFlags" -as [type];

$builtinProperties = $document.BuiltInDocumentProperties

#region Variables
$WordVariables = 'Title', 'Subject', 'Author', 'Keywords', 'Comments', 'Template', 'Last author', 'Creation date', 'Last save time', 'Number of pages', 'Company'
foreach ($var in $WordVariables)
{
	
	# This is the Property Name we are trying to find
	$pVar = "$var"
	
	[Array]$VarArgs = $pVar
	
	#Get the Property item for the Comments property
	$VarProp = [System.__ComObject].InvokeMember("Item", $binding::GetProperty, $null, $builtinProperties, $VarArgs)
	
	#Get the value of the Comments property, so we can check if it contains a version string.
	$VarPropValue = [System.__ComObject].InvokeMember("value", $binding::GetProperty, $null, $VarProp, $null);
	
	Write-Output "$Var`: $VarPropValue"
	
} #end foreach var

#endregion Variables

$document.Close($false) 
$wordApplication.Quit()

If you want to construct a hashtable with key/value pairs, you need to do two things:

1: instantiate an empty hashtable before the loop:

$hashtable = @{}

2: add the items to the hashtable instead of doing Write-Output:

$hashtable[$var] = $VarPropValue

That said… are you pulling document properties from the Word document? If I’m not mistaken, you should be able to do this a lot more simply. (Though, granted, I’ve not had nearly as much experience dealing with Word COM Objects, because Word isn’t presently installed on my machine…)

Tell you what… What does the $BuiltInProperties variable contain before the loop? (Just Write-Output that variable & copy-paste – make sure if it includes any sensitive data you snip that out!) and then also, what does $BuiltInProperties.GetType() give you? I feel like this is a very circuitous method and there might be a far simpler one available to you!

I’m using this method (Word.Application), because the Shell.Application doesn’t capture the Last Author Field (snippet from that script is below).

# Creates the Shell.Application (to capture settings)
$objShell = New-Object -ComObject Shell.Application

In that example, I’m able to capture the values I need by $FileAttributesReportAll | export-csv

I’m trying to accomplish the same results with the word.application data, but get the “Last Author” property.

PS C:\Users\grant.harrington\Documents\GitHub\PowerShell (master ↑1 +13 ~8 -0 !)
$ Write-Output $builtinProperties
System.__ComObject

PS C:\Users\grant.harrington\Documents\GitHub\PowerShell (master ↑1 +13 ~8 -0 !)
$ $BuiltInProperties.GetType()
Object reference not set to an instance of an object.
At line:1 char:1

  • $BuiltInProperties.GetType()
  •   + CategoryInfo          : OperationStopped: (:) [], NullReferenceException
      + FullyQualifiedErrorId : System.NullReferenceException
    
    
foreach ($IndividualDirectory in $SubDirectory.FullName)
{
	$Subfilesdir = Get-ChildItem $IndividualDirectory
	# Scans files and puts in an array
	
	# Creates the Shell.Application (to capture settings)
	$objShell = New-Object -ComObject Shell.Application
	
	# Assigns ParentDirectory to ComObject Namespace
	$objFolder = $objShell.namespace($IndividualDirectory)
	
	
	$Global:FileAttributesReportAll = @() 
	
	if ($Subfilesdir.Name -like "*.doc*")
	{
		foreach ($File in $Subfilesdir.Name)
		{
			$Item = $objFolder.items().item($File)
			
			$ObjFileAttributes = [ordered]@{
				"Authors"  = $objFolder.getDetailsOf($Item, 20)
				"Company"  = $objFolder.getDetailsOf($Item, 33)
				"Folder Path" = $objFolder.getDetailsOf($Item, 186)
				"Path"	   = $objFolder.getDetailsOf($Item, 189)
			} #end $ObjFileAttributes
			
			$Global:FileAttributesReport = New-Object -TypeName PSObject -Property $ObjFileAttributes
			$Global:FileAttributesReportAll += $FileAttributesReport
			
        	$FileAttributesReportAll
#region Input

[CmdletBinding(ConfirmImpact='Low')] 
Param(
    [Parameter(Mandatory=$false)]
        [ValidateScript({Test-Path $_ -PathType 'Container'})]
            [String]$WordFolder = 'c:\data' # Path to folder containing Word Docs
)

#endregion

#region Initialize

$FileList = Get-ChildItem -Path $WordFolder | where Extension -Match 'doc'
if (! $FileList) { throw "No word documents found under folder '$WordFolder'" }
$wordApplication = New-Object -ComObject word.application
$wordApplication.Visible = $false
$binding = 'System.Reflection.BindingFlags' -as [type]
$PropertyList = @('Title', 'Subject', 'Author', 'Keywords', 'Comments', 'Template', 'Last author', 'Creation date', 'Last save time', 'Number of pages', 'Company')

#endregion

#region Process

$myOutput = foreach ($FileName in $FileList.FullName) {
    Write-Verbose "Processing file '$FileName'" 
    $document = $wordApplication.documents.open($FileName)
    $builtinProperties = $document.BuiltInDocumentProperties
    $myHashTable = [Ordered]@{ FileName = $FileName }
    foreach ($Property in $PropertyList) {
        $Temp  = [System.__ComObject].invokemember('Item',$binding::GetProperty,$null,$builtinProperties,$Property)
        $Value = [System.__ComObject].InvokeMember('value', $binding::GetProperty, $null, $Temp, $null)
        $myHashTable += @{ $Property = $Value }
    }
    New-Object -TypeName PSCustomObject -Property $myHashTable
    $document.Close($false)
}
$wordApplication.Quit()

#endregion

#region Output

$myOutput | FT -a 
$myOutput | Export-Csv "$WordFolder\WordDocsSelectMetadata1.csv" -NoType
$myOutput | Out-GridView

#endregion

Please ask about anything that needs to be explained in the code above

I would discourage you against using Global variables. See this post for more information on PowerShell Arrays.

Thank you. That was what I needed to get the data to CSV where I can further work with it (create directories based on Last author, sort, etc…). I was able to take that concept and apply to a similar script I’m working with to obtain Excel metadata. I’m sure this can be expanded to other CustomObject projects as well.