Commands/Start-PSNode.ps1
function Start-PSNode { <# .SYNOPSIS Starts a PSNode Job .DESCRIPTION Starts a PSNode Job. This will run in the current context and allow you to run PowerShell or PipeScript as a #> param( # The Script Block to run in the Job [Parameter(Mandatory,ParameterSetName='ScriptBlock',Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('ScriptBlock','Action')] [ScriptBlock] $Command, # The name of the server, or the route that is being served. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Route','Host','HostHeader')] [String[]] $Server, # The cross origin resource sharing [Parameter(ValueFromPipelineByPropertyName)] [Alias('AccessControlAllowOrigin','Access-Control-Allow-Origin')] [string] $CORS = '*', # The root directory. If this is provided, the PSNode will act as a file server for this location. [Parameter(ValueFromPipelineByPropertyName)] [string] $RootPath, # The buffer size. If PSNode is acting as a file server, this is the size of the buffer that it will use to stream files. [Parameter(ValueFromPipelineByPropertyName)] [Uint32] $BufferSize = 512kb, # The number of runspaces in the PSNode's runspace pool. # As the PoolSize increases, the PSNode will be able to handle more concurrent requests and will consume more memory. [Parameter(ValueFromPipelineByPropertyName)] [Uint32] $PoolSize = 3, # The user session timeout. By default, 15 minutes. [Parameter(ValueFromPipelineByPropertyName)] [TimeSpan]$SessionTimeout, # The modules that will be loaded in the PSNode. [Parameter(ValueFromPipelineByPropertyName)] [string[]] $ImportModule, # The functions that will be loaded in the PSNode. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Functions','Function')] [Management.Automation.FunctionInfo[]] $DeclareFunction, # The aliases that will be loaded in the PSNode. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Alias', 'Aliases')] [Management.Automation.AliasInfo[]] $DeclareAlias, # Any additional types.ps1xml files to load in the PSNode. [Parameter(ValueFromPipelineByPropertyName)] [Alias('ImportTypesFile', 'ImportTypeFiles','ImportTypesFiles')] [string[]] $ImportTypeFile, # Any additional format.ps1xml files to load in the PSNode. [Parameter(ValueFromPipelineByPropertyName)] [Alias('ImportFormatsFile', 'ImportFormatFiles','ImportFormatsFiles')] [string[]] $ImportFormatFile, # If set, will allow the directories beneath RootPath to be browsed. [Parameter(ValueFromPipelineByPropertyName)] [Switch] $AllowBrowseDirectory, # If set, will execute .ps1 files located beneath the RootPath. If this is not provided, these .PS1 files will be displayed in the browser like any other file (assuming you provided a RootPath) [Parameter(ValueFromPipelineByPropertyName)] [Switch] $AllowScriptExecution, # The authentication type [Parameter(ValueFromPipelineByPropertyName=$true)] [Net.AuthenticationSchemes] $AuthenticationType = "Anonymous" ) begin { $sourceCode = ([PSCustomObject]@{PSTypeName="PipeScript.Net"}).'PSNodeJob.cs' $ClassNamespace = if ($sourceCode -match "namespace .+?[\r\n]") { @("$($matches.0)" -split '\s+')[1] } $className = if ($sourceCode -match "class .+?[\r\n]") { @("$($matches.0)" -split '\s+')[1] } $targetTypeName = "$ClassNamespace.$className" # First, make the job type if it does not exist: if (-not ($targetTypeName -as [type])) { $scriptPreface = { param($request, $response, $context, $user, $session, $application, $psNode) $inPsNode = $true $script:OutputOffset = 0 Add-Member ScriptMethod Write { param($value) if ($value -is [string]) { $buffer = [Text.Encoding]::UTF8.GetBytes($value) $this.OutputStream.Write($buffer, 0, $buffer.Length) } elseif ($value -as [byte[]]) { $buffer = $value -as [byte[]] $this.OutputStream.Write($buffer, 0, $buffer.Length) } elseif ($value) { $html = $value | Out-Html $buffer = [Text.Encoding]::UTF8.GetBytes($html) $this.OutputStream.Write($buffer, 0, $buffer.Length) } } -InputObject $response Add-Member -InputObject $Request -MemberType ScriptProperty RequestBody { if ($this._CachedRequestBody) { return $this._CachedRequestBody } if (-not $this.InputStream) { return } if (-not $this.psobject.properties.item('RequestBytes')) { $ms = [IO.MemoryStream]::new() $this.InputStream.CopyTo($ms) $this.psobject.properties.add(( [psnoteproperty]::new('RequestBytes', $ms.ToArray()) )) } if (-not $ms) { $ms = [io.memorystream]::new($this.RequestBytes) } $null = $ms.Seek(0,0) $requestBody = if ($this.ContentType -like 'text/*' -or $this.ContentType -like 'application/*') { $sr = [IO.StreamReader]::new($ms, $this.ContentEncoding) $sr.ReadToEnd() $sr.Close() $sr.Dispose() } elseif ($this.ContentType -like 'multipart/*;*') { $sr = [io.streamreader]::new($ms, [Text.Encoding]::ASCII) $sr.ReadToEnd() $sr.Close() $sr.Dispose() } else { $ms.ToArray() } $this.psobject.properties.add(( [psnoteproperty]::new('_CachedRequestBody', $requestBody) )) return $requestBody } Add-Member -InputObject $request -MemberType ScriptProperty -Name Params -Value { $requestParams = @{} if ("$($request.QueryString)" -or $Request.HasEntityBody) { if ("$($request.QueryString)") { $query = ([uri]$request.RawUrl -split '/')[-1] foreach ($_ in $query.TrimStart('?') -split '&') { if (-not $_) { continue } $key, $value = $_ -split '=' $requestParams[[Web.HttpUtility]::UrlDecode($key)] = [Web.HttpUtility]::UrlDecode($value) } } if ($request.HasEntityBody) { $rawData = $this.RequestBody $ms = [IO.MemoryStream]::new($this.RequestBytes) $boundary = if ($this.ContentType -like 'multipart/*;*') { @(@($request.ContentType -split ';')[1] -split '=')[1] } if ($boundary) { $boundaries = [Regex]::Matches($rawData, $boundary) for ($i = 0; $i -lt ($boundaries.Count - 1); $i++) { $startBoundary, $endBoundary = $boundaries[$i], $boundaries[$i + 1] $realStart = $startBoundary.Index + $startBoundary.Length $realEnd= $endBoundary.Index - 2 $boundryContent = $rawData.Substring($realStart, $realEnd - $realStart) $s = $boundryContent.IndexOf("`r`n`r`n") if ($s -ne -1) { $preamble = $boundryContent.Substring(0, $s) $s+=4 $name = foreach ($_ in $preamble -split ';') { $_ = $_.Trim() if ($_.StartsWith('name=')) { $_.Substring(5).Trim('"').Trim("'") break } } $null= $ms.Seek($realStart + $s, 0) $buffer = [byte[]]::new($realEnd - ($realStart + $s)) $bytesRead = $ms.Read($buffer,0, $buffer.Length) if ($preamble.IndexOf('Content-Type', [StringComparison]::OrdinalIgnoreCase) -eq -1) { $ms2 = [io.memorystream]::new($buffer) $sr2 = [io.StreamReader]::new($ms2, $this.ContentEncoding) $requestParams[$name] = $sr2.ReadToEnd().TrimEnd() $sr2.Dispose() $ms2.Dispose() } else { $requestParams[$name] = $buffer } } } } elseif ($rawData -match '^\s[\{\[](?!@)') { try { $parsedData = ConvertFrom-Json $rawData foreach ($k in $parsedData.psobject.properties) { $requestParams[$k.Name] = $parsedData[$k.Value] } } catch { } } else { try { $parsedData = [Web.HttpUtility]::ParseQueryString($rawData) foreach ($k in $parsedData.Keys) { $requestParams[$k] = $parsedData[$k] } } catch { } } $ms.Close() $ms.Dispose() } } return $requestParams } } $sourceCode = $sourceCode.Replace("<#ScriptPreface#>", $scriptPreface.ToString().Replace('"','""')) $addedType = if ($PSVersionTable.Platform -eq 'Unix') { $linuxRefs = "System.Web",([IO.Path].Assembly),([PSObject].Assembly), ([Net.HttpListener].Assembly), ([IO.FileInfo].Assembly), ([IO.StreamWriter].Assembly), ([Net.Cookie].Assembly), ([Security.Principal.IPrincipal].Assembly), 'System.Collections', ([Web.HttpUtility].Assembly), ([Timers.Timer].Assembly), 'System.ComponentModel.Primitives', ([Collections.Specialized.NameValueCollection].Assembly), ([Regex].Assembly),[Net.WebHeaderCollection].Assembly $linuxRefs += [PSObject].Assembly.GetReferencedAssemblies() $compilerParams = "-r:$([PSObject].Assembly.Location)", "-r:$([Hashtable].Assembly.Location)" Add-Type -TypeDefinition $sourceCode -ReferencedAssemblies $linuxRefs -IgnoreWarnings -CompilerOptions $compilerParams -PassThru } else { Add-Type -TypeDefinition $sourceCode -IgnoreWarnings -PassThru } if (-not $addedType) { Write-Error "Could not add type" return } } } process { if (-not $name) {$name = [GUID]::NewGuid() } $props = @{} + $PSBoundParameters $server = foreach ($pathToServe in $server) { @(switch -regex ($server) { "^(?!https?://)" { "http://" } "." { $server } "(?<!/)" { '/' } } ) -join '' } $props["ListenerLocation"] = $Server -replace '//$', '/' $props.Remove('Server') $props.PSNodeAction = $Command $props.Remove('Command') if (-not $props.BufferSize) {$props.BufferSize = $BufferSize} if (-not $props.CORS) { $props.CORS } $nodeJobType = $targetTypeName -as [Type] if (-not $nodeJobType) { return } $NodeProperties = foreach ($_ in $nodeJobType.GetProperties("Instance,Public")) { $_ } $NodePropertyNames = foreach ($_ in $NodeProperties) { $_.Name } foreach ($_ in @($props.Keys)) { if ($NodePropertyNames -notcontains $_) { $props.Remove($_) } } $props.Remove('Debug') $props.Remove('Verbose') $psNodeJobInstance= New-Object $targetTypeName ($name, "$Command",$Command) -Property $props if ($psNodeJobInstance) { $psNodeJobInstance.Start() $null = $PSCmdlet.JobRepository.Add($psNodeJobInstance) $psNodeJobInstance } } } |