Skip to content

Commit 6669455

Browse files
Merge pull request #30 from PowerShellWeb/Init-OpenXML
OpenXML 0.1
2 parents 4bbb254 + aedbbb7 commit 6669455

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2408
-0
lines changed

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: [StartAutomating]

.github/workflows/BuildOpenXML.yml

Lines changed: 498 additions & 0 deletions
Large diffs are not rendered by default.

Build/OpenXML.ezout.ps1

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#requires -Module EZOut
2+
# Install-Module EZOut or https://github.com/StartAutomating/EZOut
3+
$myFile = $MyInvocation.MyCommand.ScriptBlock.File
4+
$myRoot = $myFile | Split-Path | Split-Path
5+
$myModuleName = $myFile | Split-Path | Split-Path | Split-Path -Leaf
6+
Push-Location $myRoot
7+
$formatting = @(
8+
# Add your own Write-FormatView here,
9+
# or put them in a Formatting or Views directory
10+
foreach ($potentialDirectory in 'Formatting','Views','Types') {
11+
Join-Path $myRoot $potentialDirectory |
12+
Get-ChildItem -ea ignore |
13+
Import-FormatView -FilePath {$_.Fullname}
14+
}
15+
)
16+
17+
$destinationRoot = $myRoot
18+
19+
if ($formatting) {
20+
$myFormatFilePath = Join-Path $destinationRoot "$myModuleName.format.ps1xml"
21+
# You can also output to multiple paths by passing a hashtable to -OutputPath.
22+
$formatting | Out-FormatData -Module $MyModuleName -OutputPath $myFormatFilePath
23+
}
24+
25+
$types = @(
26+
# Add your own Write-TypeView statements here
27+
# or declare them in the 'Types' directory
28+
Join-Path $myRoot Types |
29+
Get-Item -ea ignore |
30+
Import-TypeView
31+
32+
)
33+
34+
if ($types) {
35+
$myTypesFilePath = Join-Path $destinationRoot "$myModuleName.types.ps1xml"
36+
# You can also output to multiple paths by passing a hashtable to -OutputPath.
37+
$types | Out-TypeData -OutputPath $myTypesFilePath
38+
}
39+
Pop-Location

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## OpenXML 0.1
2+
3+
* Initial Build of OpenXML Module (#1)
4+
* Commands:
5+
* `Get-OpenXML` (#2)
6+
* `Import-OpenXML` (#14)
7+
* `Export-OpenXML` (#15)
8+
* `Close-OpenXML` (#16)
9+
* `Copy-OpenXML` (#18)
10+
* `Set-OpenXML` (#19)
11+
* `Start-OpenXML` (#28)
12+
* `Stop-OpenXML` (#29)
13+
* Initial Extended Types
14+
* `OpenXML`
15+
* `OpenXML.get_Parts` (#17)
16+
* `OpenXML.get_Created` (#23)
17+
* `OpenXML.get_Modified` (#24)
18+
* `OpenXML.File`
19+
* `OpenXML.File.get_DocumentProperty` (#13)
20+
* `OpenXML.File` default display (#7)
21+
* `OpenXML.Excel.File`
22+
* `OpenXML.Excel.File.get_Worksheets` (#5)
23+
* `OpenXML.Excel.File.get_SharedString` (#25)
24+
* `OpenXML.Excel.Worksheet`
25+
* `OpenXML.Excel.Worksheet.get_Cell` (#6)
26+
* `OpenXML.Excel.Worksheet.get_Formula` (#26)
27+
* `OpenXML.PowerPoint.File`
28+
* `OpenXML.PowerPoint.File.get_Slides` (#8)
29+
* `OpenXML.PowerPoint.File.get_Text` (#9)
30+
* `OpenXML.PowerPoint.Slide`
31+
* `OpenXML.PowerPoint.get_Text` (#10)
32+
* `OpenXML.Word.File`
33+
* `OpenXML.Word.File.get_Text` (#11)
34+
* Sample Documents (#3)
35+
* Initial Tests (#27)
36+
* Build workflow
37+
* Building types with [EZOut](https://github.com/StartAutomating/EZOut) (#4)
38+
* Building GitHub Workflow with [PSDevOps](https://github.com/StartAutomating/PSDevOps) (#12)
39+
* Core Documentation
40+
* README (#1)
41+
* CODE_OF_CONDUCT (#20)
42+
* CONTRIBUTING (#21)
43+
* SECURITY (#22)

CODE_OF_CONDUCT.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Code of Conduct
2+
3+
We have a simple subjective code of conduct:
4+
5+
1. Be Respectful
6+
2. Be Helpful
7+
3. Do No Harm
8+
9+
Failure to follow the code of conduct may result in blocks or banishment.

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Contibuting
2+
3+
Contributing code is very welcome! So is contributing [an issue](https://github.com/PowerShellWeb/OpenXML/issues) or starting a [discussion](https://github.com/PowerShellWeb/OpenXML/discussion).
4+
5+
All projects are easier with community help, and this project is no different.
6+
7+
## Contributing Examples
8+
9+
Examples are more than welcome! To contribute an example, please open an issue describing your example and create a pull request.
10+
11+
## Contributing Code
12+
13+
If you would like to contribute code, please describe what you intend to do in an issue or two first.
14+
15+
16+
17+

Commands/Close-OpenXML.ps1

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
function Close-OpenXML {
2+
<#
3+
.SYNOPSIS
4+
Closes OpenXML files
5+
.DESCRIPTION
6+
Closes OpenXML files and streams
7+
#>
8+
param(
9+
[Parameter(ValueFromPipeline)]
10+
[PSObject]
11+
$InputObject
12+
)
13+
14+
process {
15+
if (-not $InputObject) { return }
16+
if ($InputObject -isnot [IO.Packaging.Package]) { return }
17+
if ($InputObject.MemoryStream) {
18+
try {
19+
$InputObject.MemoryStream.Close()
20+
} catch {
21+
$PSCmdlet.WriteError($_)
22+
}
23+
}
24+
25+
try {
26+
$InputObject.Close()
27+
} catch {
28+
$PSCmdlet.WriteError($_)
29+
}
30+
}
31+
32+
}

Commands/Copy-OpenXML.ps1

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
function Copy-OpenXML
2+
{
3+
<#
4+
.SYNOPSIS
5+
Copies OpenXML
6+
.DESCRIPTION
7+
Copies content from one OpenXML file to another
8+
#>
9+
param(
10+
# The destination path
11+
[Parameter(ValueFromPipelineByPropertyName)]
12+
[Alias('Destination')]
13+
[string]
14+
$DestinationPath,
15+
16+
# The input object
17+
[Parameter(ValueFromPipeline)]
18+
[PSObject]
19+
$InputObject,
20+
21+
# If set, will update existing packages.
22+
[switch]
23+
$Force
24+
)
25+
26+
process {
27+
# If the input was not a package
28+
if ($inputObject -isnot [IO.Packaging.Package]) {
29+
$loadedPackage = # see if it is a file we can load
30+
if ($InputObject -is [IO.FileInfo]) {
31+
Get-OpenXML $InputObject.FullName
32+
} elseif ($inputFile = Get-Item -ErrorAction Ignore -Path "$InputObject") {
33+
Get-OpenXML $inputFile
34+
}
35+
36+
# If it was not, return.
37+
if ($loadedPackage -isnot [IO.Packaging.Package]) { return }
38+
$InputObject = $loadedPackage
39+
}
40+
41+
# Get the absolute path of the destination, without creating the file,
42+
$unresolvedDestination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)
43+
44+
# then see if the file exists.
45+
$fileExists = Test-Path $unresolvedDestination
46+
# If it does and we are not using the -Force
47+
if ($fileExists -and -not $force) {
48+
# write an error
49+
Write-Error "$unresolvedDestionation already exists, use -Force to update" -Category ResourceExists
50+
return
51+
}
52+
# If it did not exist, create it with New-Item -Force
53+
elseif (-not $fileExists)
54+
{
55+
# this will create intermediate paths.
56+
$newFile = New-Item -ItemType File -Path $unresolvedDestination -Force
57+
if (-not $newFile) { return }
58+
}
59+
60+
# Try to open or create our package for read and write.
61+
$destinationPackage = [IO.Packaging.Package]::Open($unresolvedDestination, 'OpenOrCreate', 'ReadWrite')
62+
63+
# If we could not, we are done.
64+
if (-not $destinationPackage) { return }
65+
66+
# Get the input parts and relationships
67+
$inputPackageParts = $InputObject.GetParts()
68+
$inputPackageRelationships = $InputObject.GetRelationships()
69+
70+
# For each part in the input
71+
foreach ($inputPart in $inputPackageParts) {
72+
# Create or open a part in the destination
73+
$destinationPart =
74+
if (-not $destinationPackage.PartExists($inputPart.Uri)) {
75+
$destinationPackage.CreatePart($inputPart.Uri, $inputPart.ContentType)
76+
} else {
77+
$destinationPackage.GetPart($inputPart.Uri)
78+
}
79+
80+
# and copy the streams.
81+
$inputStream = $inputPart.GetStream()
82+
$destinationStream = $destinationPart.GetStream()
83+
$inputStream.CopyTo($destinationStream)
84+
$inputStream.Close()
85+
$destinationStream.Close()
86+
}
87+
88+
# Then, create any relationships that do not exist.
89+
foreach ($inputRelationship in $inputPackageRelationships) {
90+
if ($inputRelationship) {
91+
if (-not $destinationPackage.RelationshipExists($inputRelationship.id)) {
92+
$null = $destinationPackage.CreateRelationship(
93+
$inputRelationship.targetUri,
94+
$inputRelationship.targetMode,
95+
$inputRelationship.relationshipType,
96+
$inputRelationship.id
97+
)
98+
}
99+
}
100+
}
101+
102+
# We can now close our package, writing the file.
103+
$destinationPackage.Close()
104+
105+
# We want to open it right back up again as we output the updated file.
106+
Get-OpenXML -FilePath $unresolvedDestination
107+
}
108+
}

Commands/Export-OpenXML.ps1

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
function Export-OpenXML {
2+
<#
3+
.SYNOPSIS
4+
Exports OpenXML
5+
.DESCRIPTION
6+
Exports loaded OpenXML to a file.
7+
#>
8+
[Alias('Save-OpenXML')]
9+
param(
10+
# The file path to save the turtle graphics pattern.
11+
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
12+
[Alias('Path')]
13+
[string]
14+
$FilePath,
15+
16+
# The input object.
17+
# This must be a package loaded with this module.
18+
[Parameter(ValueFromPipeline)]
19+
[PSObject]
20+
$InputObject,
21+
22+
# If set, will force the export even if a file already exists.
23+
[switch]
24+
$Force
25+
)
26+
27+
process {
28+
# If there is no input return
29+
if (-not $InputObject) { return }
30+
# If the input is not a package, pass it thru
31+
if ($InputObject -isnot [IO.Packaging.Package]) {
32+
return $InputObject
33+
}
34+
35+
Copy-OpenXML -DestinationPath $FilePath -InputObject $inputObject -force:$Force
36+
}
37+
}
38+

Commands/Get-OpenXML.ps1

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
function Get-OpenXML
2+
{
3+
<#
4+
.SYNOPSIS
5+
Gets Open Office XML files (Excel, PowerPoint, and Word)
6+
.DESCRIPTION
7+
Gets Open Office XML files (Excel, PowerPoint, and Word) as a structured object.
8+
9+
The object contains the file path, parts, and relationships of the OpenXML document.
10+
11+
This cmdlet can be used to read the contents of .docx, .pptx, .xps, .xlsx files
12+
(or any files that are readable with [`IO.Packaging.Package`](https://learn.microsoft.com/en-us/dotnet/api/system.io.packaging.package?wt.mc_id=MVP_321542))
13+
.EXAMPLE
14+
# Get an OpenXML document
15+
Get-OpenXML -FilePath './Sample.docx'
16+
#>
17+
[CmdletBinding()]
18+
[Alias('OpenXML')]
19+
param(
20+
# The path to the OpenXML file to read
21+
[Parameter(ValueFromPipelineByPropertyName=$true)]
22+
[Alias('Fullname')]
23+
[string]
24+
$FilePath
25+
)
26+
27+
begin {
28+
29+
filter openXMLFromFile {
30+
$filePath = $_
31+
# Get the file info and read the file as a byte stream.
32+
$fileInfo = $FilePath -as [IO.FileInfo]
33+
# By reading the file with Get-Content -AsByteStream, we avoid locking the file
34+
# (or the file being locked by another process)
35+
$packageBytes = Get-Content -Path $FilePath -AsByteStream -Raw
36+
37+
# If there were no bytes, return
38+
if (-not $packageBytes) { return }
39+
40+
# Create a memory stream from the byte array
41+
$memoryStream = [IO.MemoryStream]::new($packageBytes)
42+
# and open the package from the memory stream
43+
$filePackage = [IO.Packaging.Package]::Open($memoryStream, "Open", "ReadWrite")
44+
# If that did not work, return.
45+
if (-not $filePackage) { return }
46+
47+
$filePackage.pstypenames.insert(0,'OpenXML')
48+
$filePackage.pstypenames.insert(0,'OpenXML.File')
49+
$packageContent = $filePackage.Parts
50+
$openXMLObject = $filePackage |
51+
Add-Member NoteProperty FilePath $filePath -Force -PassThru |
52+
Add-Member NoteProperty MemoryStream $memoryStream -Force -PassThru
53+
54+
# Now we can get more specific about what type of OpenXML file this is.
55+
# By looking for certain key parts, we can determine if this is a PowerPoint, Excel, or Word file.
56+
# For example, if the package contains a part with `/ppt/` in the URI,
57+
if ($filePackage.Parts.Keys -match '/ppt/') {
58+
# it is an `OpenXML.PowerPoint.File`
59+
$openXmlObject.pstypenames.insert(0, 'OpenXML.PowerPoint.File')
60+
}
61+
62+
# If the package contains a part with `/xl/` in the URI,
63+
if ($filePackage.Parts.Keys -match '/xl/') {
64+
# it is an `OpenXML.Excel.File`
65+
$openXmlObject.pstypenames.insert(0, 'OpenXML.Excel.File')
66+
}
67+
68+
# If the package contains a part with `/word/` in the URI, it is a Word file.
69+
if ($filePackage.Parts.Keys -match '/word/') {
70+
# it is an `OpenXML.Word.File`
71+
$openXmlObject.pstypenames.insert(0, 'OpenXML.Word.File')
72+
}
73+
74+
# If the package contains a part with `/Documents/` in the URI,
75+
if ($filePackage.Parts.Keys -match '/Documents/') {
76+
# it is an `OpenXML.XPS.File`
77+
$openXmlObject.pstypenames.insert(0, 'OpenXML.XPS.File')
78+
}
79+
80+
# Now we output our openXML object
81+
$OpenXMLObject
82+
}
83+
}
84+
85+
process {
86+
if ($filePath) {
87+
# Try to resolve the file path
88+
$resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath)
89+
# If we could not resolve the path, exit
90+
if (-not $resolvedPath ) { return }
91+
92+
$resolvedPath | openXMLFromFile
93+
} else {
94+
$memoryStream = [IO.MemoryStream]::new()
95+
$EmptyPackage = [io.packaging.package]::Open($memoryStream ,'Create')
96+
$EmptyPackage | Add-Member NoteProperty -Name MemoryStream -Value $memoryStream -Force
97+
$EmptyPackage.pstypenames.insert(0, 'OpenXML')
98+
$EmptyPackage
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)