1+ function Start-OpenXML {
2+ <#
3+ . SYNOPSIS
4+ Starts an OpenXML Server
5+ . DESCRIPTION
6+ Starts a read only server, using one or more OpenXML files as the storage.
7+ . NOTES
8+ If a URI in the package is requested, that URI will be returned.
9+
10+ If a path does not have an extension, it will search for an .index.html.
11+
12+ If the file was not found, a 404 code will be returned.
13+
14+ If the OpenXML archive contains a `/404.html`, the content in this file will be returned with the 404
15+
16+ If another method than GET or HEAD is used, a 405 code will be returned.
17+
18+ If the OpenXML archive contains a `/405.html`, then content in this file will be returned with the 405.
19+
20+ ### Security Implications
21+
22+ By default, this only serves content locally.
23+
24+ If this serves content to any other machine, the script will need to be running as administrator, and all of the content within the file will be exposed.
25+
26+ If the file contained PII, this could be problematic.
27+
28+ If you see this command running in an administrative process, please contact your network infrastructure and security teams
29+ . EXAMPLE
30+ $openXmlServer = Get-OpenXML ./Examples/Blank.docx |
31+ Set-OpenXML -Uri '/index.html' -ContentType text/html -Content "
32+ <h1>Hello World</h1>
33+ " |
34+ Start-OpenXML
35+
36+ Start-Process $openXmlServer.Name
37+ . EXAMPLE
38+ $openXmlServer = Get-OpenXML ./Examples/Blank.docx |
39+ Set-OpenXML -Uri '/index.html' -ContentType text/html -Content "
40+ <html>
41+ <head>
42+ <title>Hello World</title>
43+ <link rel='stylesheet' href='/css/style.css' />
44+ </head>
45+ <body>
46+ <h1>Hello World</h1>
47+ </body>
48+ </html>
49+ " |
50+ Set-OpenXML -Uri '/css/style.css' -ContentType text/css -Content "
51+ body { background-color: #000000; color: #4488ff }
52+ " |
53+ Set-OpenXML -Uri '/404.html' -ContentType text/html -Content "
54+ <html>
55+ <head>
56+ <title>Hello World</title>
57+ <link rel='stylesheet' href='/css/style.css' />
58+ </head>
59+ <body>
60+ <h1>File Not Found</h1>
61+ </body>
62+ </html>
63+ " |
64+ Start-OpenXML
65+
66+ Start-Process $openXmlServer.Name
67+ . EXAMPLE
68+ $openXmlUpdate = Get-OpenXML ./Examples/Blank.docx |
69+ Set-OpenXML -Uri '/index.html' -ContentType text/html -Content "<h1>Hello World</h1>" |
70+ Export-OpenXML ./Examples/Server.docx -Force
71+
72+ $openXmlServer = Start-OpenXML -FilePath $openXmlUpdate.FilePath
73+
74+ Start-Process $openXmlServer.Name
75+ #>
76+ param (
77+ # The path to an OpenXML file, or a glob that matches multiple OpenXML files.
78+ [Parameter (Mandatory , ValueFromPipelineByPropertyName )]
79+ [string ]
80+ $FilePath ,
81+
82+ # The Root
83+ [Parameter (ValueFromPipelineByPropertyName )]
84+ [string ]
85+ $RootUrl = " http://127.0.0.1:$ ( Get-Random - Minimum 4200 - Maximum 42000 ) /" ,
86+
87+ # The input object. This can be provided to avoid loading a file from disk.
88+ [Parameter (ValueFromPipeline )]
89+ [PSObject ]
90+ $InputObject
91+ )
92+
93+ begin {
94+ if ($PSVersionTable.PSVersion -lt ' 7.0' ) {
95+ Write-Error " This feature requires thread jobs, which are part of PowerShell Core"
96+ return
97+ }
98+ }
99+ process {
100+ # Get our OpenXML
101+ $openXml =
102+ if ($inputObject -is [IO.Packaging.Package ]) {
103+ $inputObject
104+ } else {
105+ Get-OpenXML - FilePath $FilePath
106+ }
107+
108+ # and return if we could not
109+ if (-not $openXml ) { return }
110+
111+ # Create a listener
112+ $httpListener = [Net.HttpListener ]::new()
113+ $httpListener.Prefixes.Add ($RootUrl )
114+ $httpListener.Start ()
115+
116+
117+ # Create an IO object to populate the background runspace
118+ $IO = [Ordered ]@ {
119+ HttpListener = $httpListener
120+ OpenXML = $openXml
121+ }
122+
123+ # Because this function exposes a server, we want to fire some events.
124+ # First is an approve event: `Approve-Start-OpenXML`
125+ # By using Register-EngineEvent, this can be handled
126+ $beforeEvent =
127+ New-Event - SourceIdentifier " Approve-Start-OpenXML" - MessageData $IO - Sender $MyInvocation.MyCommand - EventArguments $IO
128+
129+ # We will just wait almost no time, so that the handler can run.
130+ Start-Sleep - Milliseconds 0
131+
132+ # To reject the event, the handler can put one of three values in the `$event.MessageData`
133+
134+ if ($beforeEvent.MessageData.Rejected -or
135+ $beforeEvent.MessageData.Reject -or
136+ $beforeEvent.MessageData.No ) {
137+ }
138+
139+ # Start a thread job
140+ $startedJob = Start-ThreadJob - ScriptBlock {
141+ param ([Collections.IDictionary ]$IO )
142+
143+ # unpack our IO into local variables
144+ foreach ($variableName in $IO.Keys ) {
145+ $ExecutionContext.SessionState.PSVariable.Set ($variableName , $IO [$variableName ])
146+ }
147+
148+ # declare a little filter to serve a part
149+
150+ filter servePart {
151+ $uriPart = $_
152+ $packagePart = $package.GetPart ($uriPart )
153+ $response.ContentType = $packagePart.ContentType
154+ $partStream = $packagePart.GetStream ()
155+ $partStream.CopyTo ($response.OutputStream )
156+ $partStream.Close ()
157+ $response.Close ()
158+ }
159+
160+ # and start listening
161+ :nextRequest while ($httpListener.IsListening ) {
162+ $getContextAsync = $httpListener.GetContextAsync ()
163+ # wait in short increments to minimize CPU impact and stay snappy
164+ while (-not $getContextAsync.Wait (7 )) {
165+
166+ }
167+ # Get our listener context
168+ $context = $getContextAsync.Result
169+ # and break that into a result and response
170+ $request , $response = $context.Request , $context.Response
171+
172+ # If they asked for an inappropriate method
173+ if ($request.HttpMethod -notin ' GET' , ' HEAD' ) {
174+ # use the appropriate status code
175+ $response.StatusCode = 405
176+ foreach ($package in $openXml ) {
177+ # and serve any /405.html we find.
178+ if ($package.PartExists (" /405.html" )) {
179+ " /405.html" | servePart
180+ continue nextRequest
181+ }
182+ }
183+ # close out
184+ $response.close ()
185+ # and continue to the next request
186+ continue nextRequest
187+ }
188+
189+ # Get the local path
190+ $localPath = $request.Url.LocalPath
191+ # if it lacks an extension, look for an index.
192+ $uriPart = if ($localPath -notmatch ' \..+?$' ) {
193+ ($localPath -replace ' /$' ) + ' /index.html'
194+ } else {
195+ $localPath
196+ }
197+
198+ # If we find the part
199+ foreach ($package in $openXml ) {
200+ if ($package.PartExists ($uriPart )) {
201+ # serve it and continue
202+ $uriPart | servePart
203+ continue nextRequest
204+ }
205+ }
206+ # If we did not find a part, set the appropriate status code
207+ $response.StatusCode = 404
208+ foreach ($package in $openXml ) {
209+ # look for a 404 to serve
210+ if ($package.PartExists (" /404.html" )) {
211+ " /404.html" | servePart
212+ continue nextRequest
213+ }
214+ }
215+ # and close the respons
216+ $response.Close ()
217+ }
218+ } - ArgumentList $IO - Name $RootUrl - ThrottleLimit 100 |
219+ Add-Member NoteProperty IO $IO - Force - PassThru |
220+ Add-Member NoteProperty HttpListener $httpListener - Force - PassThru |
221+ Add-Member NoteProperty OpenXML $openXml - Force - PassThru
222+
223+ $null = New-Event - SourceIdentifier Start-OpenXML - Sender $MyInvocation.MyCommand - EventArguments $startedJob - MessageData $IO
224+
225+ $startedJob
226+
227+ }
228+ }
0 commit comments