While working on automating Microsoft Word I needed to read and write custom properties in documents. I found several resources out there. Reading worked fine, but when writing custom properties they all crashed. I finally managed to fix it though, and though I’d share my solution.
I compiled cmdlets for reading and writing both built-in and custom properties. The cmdlets are generic and work with Word, Excel and PowerPoint. Here is an example how to use with Word :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
. d:\OfficeProperties.ps1 write-host "Start Word and load a document..." -Foreground Yellow $app = New-Object -ComObject Word.Application $app.visible = $true $doc = $app.Documents.Open("d:\Properties.docx", $false, $false, $false) write-host "`nAll BUILT IN Properties:" -Foreground Yellow Get-OfficeDocBuiltInProperties $doc write-host "`nWrite to BUILT IN author property:" -Foreground Yellow $result = Set-OfficeDocBuiltInProperty "Author" "Mr. Robot" $doc write-host "Result: $result" write-host "`nRead BUILT IN author again:" -Foreground Yellow Get-OfficeDocBuiltInProperty "Author" $doc write-host "`nAll CUSTOM Properties (none if new document):" -Foreground Yellow Get-OfficeDocCustomProperties $doc write-host "`nWrite a CUSTOM property:" -Foreground Yellow $result = Set-OfficeDocCustomProperty "Hacked by" "fsociety" $doc write-host "Result: $result" write-host "`nRead back the CUSTOM property:" -Foreground Yellow Get-OfficeDocCustomProperty "Hacked by" $doc write-host "`nSave document and close Word..." -Foreground Yellow $doc.Save() $doc.Close() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($doc) | Out-Null $app.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) | Out-Null [gc]::collect() [gc]::WaitForPendingFinalizers() write-host "`nReady!" -Foreground Green |
Here is the full script (also available on my GitHub):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# PowerShell cmdlets to read & write MS Office document properties # Compiled by rlv-dan from various source online function Get-OfficeDocBuiltInProperties { [OutputType([Hashtable])] Param ( [Parameter(Mandatory=$true)] [System.__ComObject] $Document ) $result = @{} $binding = "System.Reflection.BindingFlags" -as [type] $properties = $Document.BuiltInDocumentProperties foreach($property in $properties) { $pn = [System.__ComObject].invokemember("name",$binding::GetProperty,$null,$property,$null) trap [system.exception] { continue } $result.Add($pn, [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null)) } return $result } function Get-OfficeDocBuiltInProperty { [OutputType([string],$null)] Param ( [Parameter(Mandatory=$true)] [string] $PropertyName, [Parameter(Mandatory=$true)] [System.__ComObject] $Document ) try { $comObject = $Document.BuiltInDocumentProperties($PropertyName) $binding = "System.Reflection.BindingFlags" -as [type] $val = [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$comObject,$null) return $val } catch { return $null } } function Set-OfficeDocBuiltInProperty { [OutputType([boolean])] Param ( [Parameter(Mandatory=$true)] [string] $PropertyName, [Parameter(Mandatory=$true)] [string] $Value, [Parameter(Mandatory=$true)] [System.__ComObject] $Document ) try { $comObject = $Document.BuiltInDocumentProperties($PropertyName) $binding = "System.Reflection.BindingFlags" -as [type] [System.__ComObject].invokemember("Value",$binding::SetProperty,$null,$comObject,$Value) return $true } catch { return $false } } function Get-OfficeDocCustomProperties { [OutputType([HashTable])] Param ( [Parameter(Mandatory=$true, Position=2)] [System.__ComObject] $Document ) $result = @{} $binding = "System.Reflection.BindingFlags" -as [type] $properties = $Document.CustomDocumentProperties foreach($property in $properties) { $pn = [System.__ComObject].invokemember("name",$binding::GetProperty,$null,$property,$null) trap [system.exception] { continue } $result.Add($pn, [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null)) } return $result } function Get-OfficeDocCustomProperty { [OutputType([string], $null)] Param ( [Parameter(Mandatory=$true)] [string] $PropertyName, [Parameter(Mandatory=$true)] [System.__ComObject] $Document ) try { $comObject = $Document.CustomDocumentProperties($PropertyName) $binding = "System.Reflection.BindingFlags" -as [type] $val = [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$comObject,$null) return $val } catch { return $null } } function Set-OfficeDocCustomProperty { [OutputType([boolean])] Param ( [Parameter(Mandatory=$true)] [string] $PropertyName, [Parameter(Mandatory=$true)] [string] $Value, [Parameter(Mandatory=$true)] [System.__ComObject] $Document ) try { $customProperties = $Document.CustomDocumentProperties $binding = "System.Reflection.BindingFlags" -as [type] [array]$arrayArgs = $PropertyName,$false, 4, $Value try { [System.__ComObject].InvokeMember("add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) | out-null } catch [system.exception] { $propertyObject = [System.__ComObject].InvokeMember("Item", $binding::GetProperty, $null, $customProperties, $PropertyName) [System.__ComObject].InvokeMember("Delete", $binding::InvokeMethod, $null, $propertyObject, $null) [System.__ComObject].InvokeMember("add", $binding::InvokeMethod, $null, $customProperties, $arrayArgs) | Out-Null } return $true } catch { return $false } } |
Amazing! i’ve been trying to figure it out as well!
Found lots of resources that could read but writing failed with exception errors
Thank you, Thank you