ConvertTo-ModuleService.ps1
function ConvertTo-ModuleService { <# .Synopsis Export a PowerShell module as a series of ASP.NET Handlers .Description Exports a Powershell module as a series of ASP.NET handlers .Example Import-Module Pipeworks -Force -PassThru | ConvertTo-ModuleService -Force -Allowdownload .Link Invoke-WebCommand #> [OutputType([Nullable])] param( #|Options Get-Module | Select-Object -ExpandProperty Name # The name of the module to export [ValidateScript({ if (-not (Get-Module "$_")) { $isavailable = Get-Module -ListAvailable "$_" if ($isavailable) { $isavailable | Import-Module -Global } } return $true })] [Parameter(Mandatory=$true,Position=0,ParameterSetName='LoadedModule',ValueFromPipelineByPropertyName=$true)] [string] $Name, # The order in which to display the commands [Parameter(Position=2)] [string[]] $CommandOrder, # The Google Analytics ID used for the module [string] $AnalyticsId, # The directory where the generated module will be stored. # If no directory is specified, the module will be put in Inetpub\wwwroot\ModuleName [string] $OutputDirectory, # If set, will overwrite files found in the output directory [Switch] $Force, # If set, will allow the module to be downloaded [Parameter(Position=1)] [switch]$AllowDownload, # If set, will make changes to the web.config file to work for Intranet sites (anonymous authentication will be disabled, and windows authentication will be enabled). [Switch]$AsIntranetSite, # The Kerberos realm to use for authentication. # Only works with -AsIntranetSite. # If provided, Kerberos authentication will be used instead of NTLM. # This is both faster, and more secure. [string]$Realm, # If provided, will run the site under an app pool with the credential [Management.Automation.PSCredential] $AppPoolCredential, # The port an intranet site should run on. [Uint32]$Port, # If a download URL is present, a download link will redirect to that URL. [uri]$DownloadUrl, # If set, the blog page will become the homepage of the module [Switch]$AsBlog, # If set, will add a URL rewriter rule to accept any URL that is not a real file. [Switch]$AcceptAnyUrl, # If this is set, will use this module URL as the module service URL. [Uri]$ModuleUrl, # If set, will render a CSS style [Hashtable]$Style, # If set, will create appSettings in a web.config file. This can be used to store common settings, like connection data. [Hashtable]$ConfigSetting = @{}, # The margin on either side of the module content. Defaults to 7.5%. [ValidateRange(0,100)] [Double] $MarginPercent = 3, # The margin on the left side of the module content. Defaults to 7.5%. [ValidateRange(0,100)] [Double] $MarginPercentLeft = 3, # The margin on the left side of the module content. Defaults to 7.5%. [ValidateRange(0,100)] [Double] $MarginPercentRight = 3, # The schematics used to produce the module service. # Schematics let you quickly and easily give a look or feel around data or commands, and let you parameterize your deployment with the pipeworks manifest. [Alias('Schematic')] [string[]] $UseSchematic, # If set, will run commands in a runspace for each user. If not set, users will run in a pool [Switch] $IsolateRunspace, # The size of the runspace pool that will handle request. The more runspaces in the pool, the more concurrent users [Uint16] $PoolSize = 4, # If set, will reset IIS [Switch] $IISReset, # The maximum amount of that a page can run before it times out. [Timespan] $ExecutionTimeout = [Timespan]::FromSeconds(120), # The maximum request length. [Uint32] $MaximumRequestLength = 640kb, # If set, will show the default browser when the conversion is complete [Switch] $Show, # If provided, will visit this URL after the conversion is complete. [Uri] [Alias('Page', 'ShowPage')] $Do, # If set, will run as a background job [Switch] $AsJob, # If provided, will run as a background job, with the throttle being the maximum number of background jobs [Uint32] $Throttle, # The amount of time static content will be cached for. By default, one week. [Timespan] $CacheStaticContentFor = [Timespan]::FromDays(7), # If set, will not clean the output directory. If you are trying to nest multiple pipeworks sites, this would be the way to go. [Switch] $DoNotClean, # If set, the module will be assumed to be nested beneath another module, and no DefaultDocument will be added to the web.config [Switch] $IsNested ) begin { # First up we define a lot of code that will be used throughout any module service. #region AsJobOrElevate # Drop into (almost) any script to let it be run as a background job, or auto-elevate to an admin. $asJobOrElevate = { param($CommandInfo, [switch]$OnlyCommand, [string[]]$AdditionalModules, [Hashtable]$Parameter, [Switch]$RequireAdmin) $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $isAdmin = (New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if ($AsJob -or $Throttle -or (-not $isAdmin) -and $requireAdmin) { if ($onlyCommand) { $AdditionalModules = $AdditionalModules | Select-Object -Unique $myDefinition = [ScriptBLock]::Create(" $(if ($AdditionalModules) { " Import-Module '$($AdditionalModules -join ("','"))' "}) function $commandInfo { $($commandInfo | Select-Object -ExpandProperty Definition) } ") } else { $myModule = $CommandInfo.ScriptBlock.Module $AdditionalModules += $myModule | Split-Path $AdditionalModules += $myModule.RequiredModules | Split-Path $AdditionalModules = $AdditionalModules | Select-Object -Unique $myDefinition = [ScriptBLock]::Create(" $(if ($AdditionalModules) { " Import-Module '$($AdditionalModules -join ("','"))' "}) ") } $null = $Parameter.Remove('AsJob') $null = $Parameter.Remove('Throttle') $null = $Parameter.Remove('RequireAdmin') $myJob= [ScriptBLock]::Create("" + { param([Hashtable]$parameter) } + $myDefinition + " $commandInfo `@parameter ") if ($Throttle) { $jobLaunched= $false do { if ($myJobs) { $myJobs | Receive-Job } $runningJobs = $myJobs | Where-Object { $_.State -ne 'Running' } if ($runningJobs) { $runningJobs | Remove-Job -Force } if ($myJobs.Count -lt $throttle) { $null = Start-Job -Name "${MyCmd}_Background_Job" -ScriptBlock $myJob -ArgumentList $Parameter $JobLaunched = $true } $myJobs = Get-Job -Name "${MyCmd}_Background_Job" -ErrorAction SilentlyContinue Write-Progress "Waiting for Jobs to Complete" "$($myJobs.Count) Running" -Id $ProgressId } until ($jobLaunched) $myJobs = Get-Job -Name "${MyCmd}_Background_Job" -ErrorAction SilentlyContinue $myJobs | Wait-Job | Receive-Job return } elseif ($asJob) { return Start-Job -ScriptBlock $myJob -ArgumentList $Parameter -Name "${CommandInfo}_Background_Job" } elseif ((-not $isAdmin) -and $RequireAdmin) { $fullCommand = " `$parameter = $(Write-PowerShellHashtable -InputObject $parameter) & { $myJob } `$parameter " $encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($fullCommand)) return Start-Process powershell -Verb Runas -ArgumentList '-encodedCommand', $encodedCommand -PassThru } } } #endregion AsJobOrElevate # All command services have to have a lot packed into each runspace, so a bit has to happen to set things up # - An InitialSessionState has to be created for the new runspace # - Potentially harmful or useless low-rights commands are removed from the runspace # - "Common" Functions are embedded into each handler #region ResolveFinalUrl # This allows us to determine the real URL the service is being called by, including parts from URL redirection $resolveFinalUrl = { # The tricky part is resolving the real URL of the service. # Split out the protocol $resolveUrlStartedAt = [DateTime]::Now $protocol = $request['Server_Protocol'].Split("/", [StringSplitOptions]::RemoveEmptyEntries)[0] # And what it thinks it called the server $serverName= $request['Server_Name'] $port = $request.Url.Port # And the relative path beneath that URL $shortPath = [IO.Path]::GetDirectoryName($request['PATH_INFO']) # Put them all together if (($protocol -eq 'http' -and $port -eq 80) -or ($protocol -eq 'https' -and $port -eq 443)) { $remoteCommandUrl = $Protocol + '://' + $ServerName.Replace('\', '/').TrimEnd('/') + '/' + $shortPath.Replace('\','/').TrimStart('/') } else { $remoteCommandUrl = $Protocol + '://' + $ServerName.Replace('\', '/').TrimEnd('/') + ':' + $port + '/' + $shortPath.Replace('\','/').TrimStart('/') } # Now, if the pages was anything but Default, add the .ashx reference $finalUrl = if ($request['Url'].EndsWith("Default.ashx", [StringComparison]"InvariantCultureIgnoreCase")) { $u = $request['Url'].ToString() $remoteCommandUrl.TrimEnd("/") + "/" # $remoteCommandUrl.TrimEnd("/") + $u.Substring($u.LastIndexOf("/")) } elseif ($request['Url'].EndsWith("Module.ashx", [StringComparison]"InvariantCultureIgnoreCase")) { $u = $request['Url'].ToString() $remoteCommandUrl.TrimEnd("/") + $u.Substring($u.LastIndexOf("/")) } else { $remoteCommandUrl.TrimEnd("/") + "/" } $fullUrl = "$($request.Url)" if ($request -and $request.Params -and $request.Params["HTTP_X_ORIGINAL_URL"]) { #region Determine the Relative Path, Full URL, and Depth $originalUrl = $context.Request.ServerVariables["HTTP_X_ORIGINAL_URL"] $urlString = $request.Url.ToString().TrimEnd("/") $pathInfoUrl = $urlString.Substring(0, $urlString.LastIndexOf("/")) $protocol = ($request['Server_Protocol'].Split("/", [StringSplitOptions]"RemoveEmptyEntries"))[0] $serverName= $request['Server_Name'] $port= $request.Url.Port $fullOriginalUrl = if (($Protocol -eq 'http' -and $port -eq 80) -or ($Protocol -eq 'https' -and $port -eq 443)) { $protocol+ "://" + $serverName + $originalUrl } else { $protocol+ "://" + $serverName + ':' + $port + $originalUrl } $rindex = $fullOriginalUrl.IndexOf($pathInfoUrl, [StringComparison]"InvariantCultureIgnoreCase") $relativeUrl = $fullOriginalUrl.Substring(($rindex + $pathInfoUrl.Length)) $rootUrl = $fullOriginalUrl.Substring(0, $pathInfoUrl.Length) if ($relativeUrl -like "*/*") { $depth = @($relativeUrl -split "/" -ne "").Count - 1 if ($fullOriginalUrl.EndsWith("/")) { $depth++ } } else { $depth = 0 } $RelativeDepth = "../" * $depth #endregion Determine the Relative Path, Full URL, and Depth $fullUrl = $fullOriginalUrl } if (-not $rootUrl) { $rootUrl = $fullurl.Substring(0, $fullUrl.LastIndexOf("/")) } $serviceUrl = $fullUrl $timeSpentResolvingURL = [DateTime]::now - $resolveUrlStartedAt } #endregion ResolveFinalUrl #region UnpackItem # This allows us to take items with compressed data and expand them $unpackItem = { $item = $_ $item.psobject.properties | Where-Object { ('Timestamp', 'RowKey', 'TableName', 'PartitionKey' -notcontains $_.Name) -and (-not "$($_.Value)".Contains(' ')) }| ForEach-Object { try { $expanded = Expand-Data -CompressedData $_.Value $item | Add-Member NoteProperty $_.Name $expanded -Force } catch{ Write-Verbose $_ } } $item.psobject.properties | Where-Object { ('Timestamp', 'RowKey', 'TableName', 'PartitionKey' -notcontains $_.Name) -and (-not "$($_.Value)".Contains('<')) }| ForEach-Object { try { $fromMarkdown = ConvertFrom-Markdown -Markdown $_.Value $item | Add-Member NoteProperty $_.Name $fromMarkdown -Force } catch{ Write-Verbose $_ } } $item } #endregion UnpackItem #region RefreshLatest $refreshLatest = { if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) $latest = Search-AzureTable -TableName $pipeworksManifest.Table.Name -Filter "PartitionKey eq '$PartitionKey'" -Select Timestamp, DatePublished, PartitionKey, RowKey -StorageAccount $storageAccount -StorageKey $storageKey | Sort-Object -Descending { if ($_.DatePublished) { [DateTime]$_.DatePublished } else { [DateTime]$_.Timestamp } } | Select-Object -First 1 | Get-AzureTable -TableName $pipeworksManifest.Table.Name } #endregion RefreshLatest # Writing the handler for a command actually involves writing several handlers, # so we'll make this it's own little inline tool. $writeSimpleHandler = { param($cSharp, [Switch]$ShareRunspace, [Uint16]$PoolSize, [Switch]$ImportsPipeworks, [string[]]$EmbeddedCommand) # Blacklist "bad" functions, and directory traversal $functionBlackList = 65..90 | ForEach-Object -Begin { "ImportSystemModules", "Disable-PSRemoting", "Restart-Computer", "Clear-Host", "cd..", "cd\\", "more" } -Process { [string][char]$_ + ":" } if (-not $script:FunctionsInEveryRunspace) { $script:FunctionsInEveryRunspace = 'ConvertFrom-Markdown', 'Confirm-Person', 'Get-Person', 'Get-Web', 'Get-PipeworksManifest', 'Get-WebConfigurationSetting', 'Get-FunctionFromScript', 'Get-Walkthru', 'Get-WebInput', 'New-RssItem', 'Invoke-WebCommand', 'Out-RssFeed', 'Request-CommandInput', 'New-Region', 'New-WebPage', 'Out-Html', 'Write-Css', 'Write-Host', 'Write-Link', 'Write-ScriptHTML', 'Write-WalkthruHTML', 'Write-PowerShellHashtable', 'Compress-Data', 'Expand-Data', 'Import-PSData', 'Export-PSData', 'ConvertTo-ServiceUrl', 'Get-SecureSetting', 'Search-Engine', 'Get-Hash' } $embedSection = "" if (-not $ImportsPipeworks) { if (-not $EmbeddedCommand) { $EmbeddedCommand = $script:FunctionsInEveryRunspace } $EmbeddedCommand = $EmbeddedCommand | Select-Object -Unique $embedSection += foreach ($func in (Get-Command -Name $EmbeddedCommand -CommandType Function)) { @" string compressed$($func.Name.Replace('-', ''))Defintion = "$(Compress-Data -String $func.Definition.ToString())"; byte[] binaryDataFor$($func.Name.Replace('-', '')) = System.Convert.FromBase64String(compressed$($func.Name.Replace('-', ''))Defintion); System.IO.MemoryStream memoryStreamFor$($func.Name.Replace('-', '')) = new System.IO.MemoryStream(); memoryStreamFor$($func.Name.Replace('-', '')).Write(binaryDataFor$($func.Name.Replace('-', '')), 0, binaryDataFor$($func.Name.Replace('-', '')).Length); memoryStreamFor$($func.Name.Replace('-', '')).Seek(0, 0); System.IO.Compression.GZipStream decompressorFor$($func.Name.Replace('-', '')) = new System.IO.Compression.GZipStream(memoryStreamFor$($func.Name.Replace('-', '')), System.IO.Compression.CompressionMode.Decompress); System.IO.StreamReader readerFor$($func.Name.Replace('-', '')) = new System.IO.StreamReader(decompressorFor$($func.Name.Replace('-', ''))); string decompressedDefinitionFor$($func.Name.Replace('-', '')) = readerFor$($func.Name.Replace('-', '')).ReadToEnd(); SessionStateFunctionEntry $($func.Name.Replace('-',''))Command = new SessionStateFunctionEntry( "$($func.Name)", decompressedDefinitionFor$($func.Name.Replace('-', '')) ); iss.Commands.Add($($func.Name.Replace('-',''))Command); memoryStreamFor$($func.Name.Replace('-', '')).Close(); memoryStreamFor$($func.Name.Replace('-', '')).Dispose(); decompressorFor$($func.Name.Replace('-', '')).Close(); decompressorFor$($func.Name.Replace('-', '')).Dispose(); readerFor$($func.Name.Replace('-', '')).Close(); "@ } # Web handlers are essentially embedded C#, compiled on their first use. The webCommandSequence class, # defined within this quite large herestring, is a bridge used to invoke PowerShell within a web handler. } $webCmdSequence = @" public class WebCommandSequence { public static InitialSessionState InitializeRunspace(string[] module) { InitialSessionState iss = InitialSessionState.CreateDefault(); if (module != null) { iss.ImportPSModule(module); } $embedSection string[] commandsToRemove = new String[] { "$($functionBlacklist -join '","')"}; foreach (string cmdName in commandsToRemove) { iss.Commands.Remove(cmdName, null); } return iss; } public static void InvokeScript(string script, HttpContext context, object arguments, bool throwError, bool shareRunspace) { PowerShell psCmd = PowerShell.Create(); psCmd.Commands.Clear(); bool justLoaded = false; Runspace runspace; RunspacePool runspacePool; PSInvocationSettings invokeWithHistory = new PSInvocationSettings(); invokeWithHistory.AddToHistory = true; PSInvocationSettings invokeWithoutHistory = new PSInvocationSettings(); invokeWithHistory.AddToHistory = false; if (! shareRunspace) { if (context.Session["UserRunspace"] == null) { justLoaded = true; InitialSessionState iss = WebCommandSequence.InitializeRunspace(null); Runspace rs = RunspaceFactory.CreateRunspace(iss); rs.ApartmentState = System.Threading.ApartmentState.STA; rs.ThreadOptions = PSThreadOptions.ReuseThread; rs.Open(); psCmd.Runspace = rs; context.Session.Add("UserRunspace",psCmd.Runspace); psCmd. AddCommand("Set-ExecutionPolicy", false). AddParameter("Scope", "Process"). AddParameter("ExecutionPolicy", "Bypass"). AddParameter("Force", true). Invoke(null, invokeWithoutHistory); psCmd.Commands.Clear(); } runspace = context.Session["UserRunspace"] as Runspace; if (context.Application["Runspaces"] == null) { context.Application["Runspaces"] = new Hashtable(); } if (context.Application["RunspaceAccessTimes"] == null) { context.Application["RunspaceAccessTimes"] = new Hashtable(); } if (context.Application["RunspaceAccessCount"] == null) { context.Application["RunspaceAccessCount"] = new Hashtable(); } Hashtable runspaceTable = context.Application["Runspaces"] as Hashtable; Hashtable runspaceAccesses = context.Application["RunspaceAccessTimes"] as Hashtable; Hashtable runspaceAccessCounter = context.Application["RunspaceAccessCount"] as Hashtable; if (! runspaceAccessCounter.Contains(runspace.InstanceId.ToString())) { runspaceAccessCounter[runspace.InstanceId.ToString()] = (int)0; } runspaceAccessCounter[runspace.InstanceId.ToString()] = ((int)runspaceAccessCounter[runspace.InstanceId.ToString()]) + 1; runspaceAccesses[runspace.InstanceId.ToString()] = DateTime.Now; if (! runspaceTable.Contains(runspace.InstanceId.ToString())) { runspaceTable[runspace.InstanceId.ToString()] = runspace; } runspace.SessionStateProxy.SetVariable("Request", context.Request); runspace.SessionStateProxy.SetVariable("Response", context.Response); runspace.SessionStateProxy.SetVariable("Session", context.Session); runspace.SessionStateProxy.SetVariable("Server", context.Server); runspace.SessionStateProxy.SetVariable("Cache", context.Cache); runspace.SessionStateProxy.SetVariable("Context", context); runspace.SessionStateProxy.SetVariable("Application", context.Application); runspace.SessionStateProxy.SetVariable("JustLoaded", justLoaded); runspace.SessionStateProxy.SetVariable("IsSharedRunspace", false); psCmd.Runspace = runspace; psCmd.AddScript(@" `$timeout = (Get-Date).AddMinutes(-20) `$oneTimeTimeout = (Get-Date).AddMinutes(-1) foreach (`$key in @(`$application['Runspaces'].Keys)) { if ('Closed', 'Broken' -contains `$application['Runspaces'][`$key].RunspaceStateInfo.State) { `$application['Runspaces'][`$key].Dispose() `$application['Runspaces'].Remove(`$key) continue } if (`$application['RunspaceAccessTimes'][`$key] -lt `$Timeout) { `$application['Runspaces'][`$key].CloseAsync() continue } } ").Invoke(); psCmd.Commands.Clear(); psCmd.AddScript(script, false); if (arguments is IDictionary) { psCmd.AddParameters((arguments as IDictionary)); } else if (arguments is IList) { psCmd.AddParameters((arguments as IList)); } Collection<PSObject> results = psCmd.Invoke(); } else { if (context.Application["RunspacePool"] == null) { justLoaded = true; InitialSessionState iss = WebCommandSequence.InitializeRunspace(null); RunspacePool rsPool = RunspaceFactory.CreateRunspacePool(iss); rsPool.SetMaxRunspaces($PoolSize); rsPool.ApartmentState = System.Threading.ApartmentState.STA; rsPool.ThreadOptions = PSThreadOptions.ReuseThread; rsPool.Open(); psCmd.RunspacePool = rsPool; context.Application.Add("RunspacePool",rsPool); /* // Initialize the pool Collection<IAsyncResult> resultCollection = new Collection<IAsyncResult>(); for (int i =0; i < $poolSize; i++) { PowerShell execPolicySet = PowerShell.Create(). AddScript(@" Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force #INSERTEVERYSECTIONIFNEEDED ", false); execPolicySet.RunspacePool = rsPool; resultCollection.Add(execPolicySet.BeginInvoke()); } foreach (IAsyncResult lastResult in resultCollection) { if (lastResult != null) { lastResult.AsyncWaitHandle.WaitOne(); } } */ psCmd.Commands.Clear(); } psCmd.RunspacePool = context.Application["RunspacePool"] as RunspacePool; string newScript = @"param(`$Request, `$Response, `$Server, `$session, `$Cache, `$Context, `$Application, `$JustLoaded, `$IsSharedRunspace, [Parameter(ValueFromRemainingArguments=`$true)]`$args) " + script; psCmd.AddScript(newScript, false); if (arguments is IDictionary) { psCmd.AddParameters((arguments as IDictionary)); } else if (arguments is IList) { psCmd.AddParameters((arguments as IList)); } psCmd.AddParameter("Request", context.Request); psCmd.AddParameter("Response", context.Response); psCmd.AddParameter("Session", context.Session); psCmd.AddParameter("Server", context.Server); psCmd.AddParameter("Cache", context.Cache); psCmd.AddParameter("Context", context); psCmd.AddParameter("Application", context.Application); psCmd.AddParameter("JustLoaded", justLoaded); psCmd.AddParameter("IsSharedRunspace", true); Collection<PSObject> results; try { results = psCmd.Invoke(); } catch (Exception ex) { if ( (String.Compare(ex.GetType().FullName, "System.Management.Automation.ParameterBindingValidationException") == 0) || (String.Compare(ex.GetType().FullName, "System.Management.Automation.RuntimeException") == 0) ) { // Parameter validation exception: clean it up a little. ErrorRecord errRec = ex.GetType().GetProperty("ErrorRecord").GetValue(ex, null) as ErrorRecord; if (errRec != null) { try { context.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; } catch { } context.Response.Write("<span class='ui-state-error' color='red'>" + errRec.InvocationInfo.PositionMessage + "</span><br/>"); } } else { throw ex; } } psCmd.Dispose(); } foreach (ErrorRecord err in psCmd.Streams.Error) { if (throwError) { if (err.Exception != null) { if (err.Exception.GetType().GetProperty("ErrorRecord") != null) { ErrorRecord errRec = err.Exception.GetType().GetProperty("ErrorRecord").GetValue(err.Exception, null) as ErrorRecord; if (errRec != null) { //context.Response.StatusCode = (int)System.Net.HttpStatusCode.PreconditionFailed; //context.Response.StatusDescription = errRec.InvocationInfo.PositionMessage; context.Response.Write("<span class='ui-state-error' style='line-height:200%' color='red'>" + err.Exception.ToString() + errRec.InvocationInfo.PositionMessage + "</span><br/>"); } //context.Response.Flush(); } else { context.AddError(err.Exception); } } } else { context.Response.Write("<span class='ui-state-error' style='line-height:200%' color='red'>" + err.Exception.ToString() + err.InvocationInfo.PositionMessage + "</span><br/>"); } } if (psCmd.InvocationStateInfo.Reason != null) { if (throwError) { context.AddError(psCmd.InvocationStateInfo.Reason); } else { context.Response.Write("<span class='ui-state-error' style='line-height:200%' color='red'>" + psCmd.InvocationStateInfo.Reason + "</span>"); } } } } "@ $webCommandSequence = $webCmdSequence if ($pipeworksManifest.Every -and $pipeworksManifest.Every -is [Hashtable]) { $everySection = "" $n = 1 foreach ($kv in $pipeworksManifest.Every.GetEnumerator()) { $interval = $kv.Key $everyAction = $kv.Value $everySection += " `$everyTimer${n} = New-Object Timers.Timer -Property @{ Interval = ([Timespan]'$interval').TotalMilliseconds } `$global:firstPulse = Get-Date Register-ObjectEvent -InputObject `$everyTimer${n} -EventName Elapsed -SourceIdentifier EveryAction${n} -Action { $everyAction } `$everyTimer${n}.Start() " $n++ } $webCommandSequence = $webCommandSequence.Replace('#INSERTEVERYSECTIONIFNEEDED', $everySection.Replace('"', '""')) } @" <%@ WebHandler Language="C#" Class="Handler" %> <%@ Assembly Name="System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %> using System; using System.Web; using System.Web.SessionState; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; $webCommandSequence public class Handler : IHttpHandler, IRequiresSessionState { public void ProcessRequest (HttpContext context) { $cSharp } public bool IsReusable { get { return true; } } } "@ } } process { $theModule = Get-Module $name $theModulePaths = @($theModule| Split-Path) $theModulePaths += @($theModule.RequiredModules | Split-Path) $launched = . $asJobOrElevate $MyInvocation.MyCommand -additionalModules $theModulePaths -Parameter $psBoundParameters -RequireAdmin if ($launched) { return $launched} if ($psCmdlet.ParameterSetName -eq 'LoadedModule') { $module = Get-Module $name | Select-Object -First 1 if (-not $module ) { return } # Skip "accidental" modules if ($module.Path -like "*.ps1") { return } $global:PipeworksManifest = $null if (-not $psBoundParameters.outputDirectory) { $outputDirectory = "${env:SystemDrive}\inetpub\wwwroot\$($Module.Name)\" $outDirWasSet = $false } else { $outDirWasSet = $true } if ((Test-Path $outputDirectory) -and (-not $force)) { Write-Error "$outputDirectory exists, use -Force to overwrite" return } if (-not $DoNotClean) { Write-Progress "Cleaning Output Directory" "$outputDirectory" Remove-Item $outputDirectory -Recurse -Force -ErrorVariable Issues } $null = New-Item -Path $outputDirectory -Force -ItemType Directory Push-Location $outputDirectory $null = New-Item -Path "$outputDirectory\bin" -Force -ItemType Directory # Urls to Rewrite stores the result. Each handler will need to rewrite several URLs for the functionality to work as expected $urlsToRewrite = @{} # To create a web command, we actually need to create several handlers and pages, depending on the options specified. $moduleNumber = 0 $realModule = $module foreach ($m in $realModule) { if (-not $m) { continue } $moduleRoot = Split-Path $m.Path $ManifestPath = Join-Path $moduleRoot "$($module.Name).psd1" if (-not (Test-Path $ManifestPath)) { " # Module Manifest autogenerated by PowerShell Pipeworks. @{ ModuleVersion = 0.1 ModuleToProcess = '$($module.Path | Split-Path -Leaf)' }" | Set-Content $manifestPath } #region Initialize Pipeworks Manifest $pipeworksManifestPath = Join-Path $moduleRoot "$($module.Name).Pipeworks.psd1" $pipeworksManifest = if (Test-Path $pipeworksManifestPath) { try { & ([ScriptBlock]::Create( "data -SupportedCommand Add-Member, New-WebPage, New-Region, Write-CSS, Write-Ajax, Out-Html, Write-Link { $( [ScriptBlock]::Create([IO.File]::ReadAllText($pipeworksManifestPath)) )}")) } catch { Write-Error "Could not read pipeworks manifest" } } if (-not $pipeworksManifest) { $pipeworksManifest = @{ Pages = @{} Posts = @{} WebCommands = @{} Assets = @{} Javascript = @{} Download = @{} CSS = @{} } } if ($pipeworksManifest.Css) { foreach ($cssItem in $pipeworksManifest.Css.GetEnumerator()) { if ($cssItem.Value -like "*.less") { if ($cssItem.Value -like "http*:*") { # Public LESS file, download and compile $lessCssFile = Get-Web -Url "$($cssItem.Value)" -UseWebRequest $compiledLess = Use-Less -LessCss $lessCssFile $lessDest = Join-Path "$moduleRoot\CSS" (([uri]$cssItem.Value).Segments[-1] -ireplace "\.less", ".css") if (-not (Test-Path "$moduleRoot\CSS")) { $null = New-Item -ItemType Directory -Path "$moduleRoot\CSS" -Force } [IO.File]::WriteAllText($lessDest, $compiledLess) } elseif ($cssItem.Value -like "/*") { # Private LESS file, resolve and compile $lessCssFile = [IO.File]::ReadAllText((Join-Path $moduleRoot $cssItem.Value)) $compiledLess = Use-Less -LessCss $lessCssFile $lessDest = (Join-Path $moduleRoot $cssItem.Value) -ireplace "\.less", ".css" [IO.File]::WriteAllText($lessDest, $compiledLess) } } } } #region Inherit Settings from the Pipeworks Manifest if (-not ($Style -and $PipeworksManifest.Style)) { $Style = $PipeworksManifest.Style } # If there's no CSS style set, create a default one if (-not $Style) { $Style = @{ Body = @{ 'Font-Family' = "'Segoe UI', 'Segoe UI Symbol', Helvetica, Arial, sans-serif" } } } if (-not $psBoundParameters.MarginPercent -or ($psBoundParameters.MarginPercentLeft -and $psBoundParameters.MarginPercentRight)) { $marginPercentLeftString = "3%" $marginPercentRightString= "3%" } else { if ($psBoundParameters.MarginPercent) { $marginPercentLeftString = $MarginPercent + "%" $marginPercentRightString = $MarginPercent + "%" } else { $marginPercentLeftString = $MarginPercentLeft+ "%" $marginPercentRightString = $MarginPercentRight+ "%" } } if ($pipeworksManifest.SecureSetting) { foreach ($configSettingName in $pipeworksManifest.SecureSetting) { if (-not $configSettingName) { continue } $settingValue = Get-SecureSetting -Name $configSettingName -ValueOnly -Type String if ($settingValue) { $configSetting[$configSettingName] = $settingValue } } } if ($pipeworksManifest.SecureSettings) { foreach ($configSettingName in $pipeworksManifest.SecureSettings) { if (-not $configSettingName) { continue } $settingValue = Get-SecureSetting -Name $configSettingName -ValueOnly -Type String if ($settingValue) { $configSetting[$configSettingName] = $settingValue } } } # If there's no analyticsId provided, and one exists in the pipeworks manifest, use it if (-not $analyticsId -and $pipeworksManifest.AnalyticsId) { $analyticsId = $pipeworksManifest.AnalyticsId } if (-not $AsIntranetSite -and $pipeworksManifest.AsIntranetSite) { $AsIntranetSite = $true } if ($pipeworksManifest.AppPoolUser) { if ($pipeworksManifest.AppPoolPasswordSetting -and (Get-SecureSetting -Name $pipeworksManifest.AppPoolPasswordsetting)) { $newCred = New-Object Management.Automation.PSCredential $pipeworksManifest.AppPoolUser, (ConvertTo-SecureString -AsPlainText -Force (Get-SecureSetting $pipeworksManifest.AppPoolPasswordSetting -ValueOnly)) $appPoolCredential = $newCred } elseif (Get-SecureSetting -Name "${Module}AppPoolPassword") { $newCred = New-Object Management.Automation.PSCredential $pipeworksManifest.AppPoolUser, (ConvertTo-SecureString -AsPlainText -Force (Get-SecureSetting "${Module}AppPoolPassword" -ValueOnly)) $appPoolCredential = $newCred } else { $cred = Get-Credential -UserName $pipeworksManifest.AppPoolUser Add-SecureSetting -Name "${Module}AppPoolPassword" -String $cred.GetNetworkCredential().Password $AppPoolCredential = $cred } } if (-not $AllowDownload -and $pipeworksManifest.AllowDownload) { $AllowDownload = $true } if (-not $port -and ($pipeworksManifest.Port -as [uint32])) { $port = $pipeworksManifest.Port } $moduleBlogTitle = if ($pipeworksManifest.Blog.Name) { $pipeworksManifest.Blog.Name } else { $module.Name } $moduleBlogDescription = if ($pipeworksManifest.Blog.Description) { $pipeworksManifest.Blog.Description } else { $module.Description } $moduleBlogLink = if ($pipeworksManifest.Blog.Link) { $pipeworksManifest.Blog.Link } else { "Blog.html" } if (-not $PipeworksManifest.Pages) { $PipeworksManifest.Pages = @{} } if (-not $PipeworksManifest.Javascript) { $PipeworksManifest.Javascript= @{} } if (-not $PipeworksManifest.CssFile) { $PipeworksManifest.CssFile = @{} } if (-not $PipeworksManifest.AssetFile) { $PipeworksManifest.AssetFile = @{} } #region Inherit Settings from the Pipeworks Manifest #endregion Initialize Pipeworks Manifest # Run the ezformat file, if present (and EZOut is loaded) if ((Test-Path "$moduleRoot\$($m.Name).ezformat.ps1") -and (Get-Module EZOut)) { & "$moduleRoot\$($m.Name).ezformat.ps1" } $realModulePath = $m.Path $moduleNumber++ if ($AllowDownload) { # If AllowDownload is set, create a .zip file to hold the module Write-Progress "Creating download" "Adding $($m) to zip file" $moduleZip = Join-Path $outputdirectory "$($m.Name).$($m.Version).Zip" if ($moduleNumber -eq 1 -and (Test-Path $moduleZip)) { Remove-Item $moduleZip -Force } $tempModulePath = New-Item "$env:Temp\TempModule$(Get-Random)" -ItemType Directory $tempModuleDir = New-Item "$tempModulePath\$($m.Name)" -ItemType Directory # By looping thru all files with Get-ChildItem, hidden files get skipped. $moduleFiles = @(Get-ChildItem -Path $moduleRoot -Recurse | Where-Object { -not $_.psIsContainer } | Copy-Item -Destination { $newPath = $_.FullName.Replace($moduleRoot, $tempModuleDir) $newDir = $newPAth |Split-Path if (-not (Test-Path $newDir)) { $null = New-Item -ItemType Directory -Path "$newDir" -Force } Write-Progress "Copying $($req.name)" "$newPath" $newPath } -passThru) # $null = Copy-ToZip -File $tempModuleDir -ZipFile $moduleZip -HideProgress if ($m.RequiredModules) { foreach ($requiredModuleInfo in $m.RequiredModules) { $requiredRoot = ($requiredModuleInfo | Split-Path) $tempRequiredModuledir = New-Item "$tempModulePath\$($requiredModuleInfo.Name)" -ItemType Directory $moduleFiles += Get-ChildItem $requiredRoot -Recurse | Where-Object { -not $_.psIsContainer } | Copy-Item -Destination { $newPAth = $_.FullName.Replace($requiredRoot, $tempRequiredModuleDir) $newDir = $newPAth |Split-Path if (-not (Test-Path $newDir)) { $null = New-Item -ItemType Directory -Path "$newDir" -Force } Write-Progress "Copying $($req.name)" "$newPath" $newPath } -passthru } } $moduleList = @($RealModule.RequiredModules | Select-Object -ExpandProperty Name) + $realModule.Name # Add an installer $installer = @' echo "Installing modules from %~dp0" '@ $allUsersInstaller = @' echo "Installing modules from %~dp0" '@ $modulePaths = @() foreach ($m in $moduleList) { $modulePaths += ('.\' + $m) $installer += @" xcopy "%~dp0$m" "%userprofile%\Documents\WindowsPowerShell\Modules\$m" /y /s /i /d "@ $allUsersInstaller += @" xcopy "%~dp0$m" "%windir%\system32\WindowsPowerShell\v1.0\Modules\$m" /y /s /i /d "@ } # Add shortcut items, if found in the pipeworks manifest if ($pipeworksManifest.Shortcut) { $shortcutsFile = "`$moduleName = '$($realModule.Name)'" + { $shell = New-Object -ComObject WScript.Shell $startRoot = "$home\AppData\Roaming\Microsoft\Windows\Start Menu\Programs" $moduleSubFolder = Join-Path $startRoot $moduleName if (-not (Test-Path $moduleSubFolder)) { $null = New-Item -ItemType Directory -Path $moduleSubFolder } $modulefolder = Join-Path "$home\Documents\WindowsPowerShell\Modules" $moduleName } $installer += @' powershell -ExecutionPolicy Bypass -File "%~dp0AddShortcuts.ps1" '@ foreach ($shortcutInfo in $pipeworksManifest.ShortCut.Getenumerator()) { if ($shortcutInfo.Value -notlike "http*") { $shortcutsFile += @" `$sh = `$shell.CreateShortcut("`$moduleSubFolder\$($shortcutInfo.Key).lnk") `$sh.WorkingDirectory = `$moduleFolder `$sh.TargetPath = "`$psHome\powershell.exe" `$sh.Arguments = '-executionpolicy bypass -windowstyle minimized -sta -command `$moduleDir = Join-Path `$env:UserProfile ''Documents\WindowsPowerShell\Modules'';Push-Location `$moduleDir;Import-Module `"$($modulePaths -join "`",`"")`";Pop-Location;$($shortcutInfo.Value)' `$sh.Save() "@ } else { $shortcutsFile += @" `$sh = `$shell.CreateShortcut("`$moduleSubFolder\$($shortcutInfo.Key).url") `$sh.TargetPath = "$($shortcutInfo.Value)" `$sh.Save() "@ } } $shortcutsFile | Set-Content "$tempModulePath\AddShortcuts.ps1" } $installer | Set-Content "$tempModulePath\Install.cmd" $allUsersInstaller | Set-Content "$tempModulePath\InstallForAllUsers.cmd" Get-ChildItem $tempModulePath -Recurse | Out-Zip -zipFile $moduleZip -commonRoot $tempModulePath | Out-Null if (Test-Path $moduleZip) { # If there's a module.zip, it might have the wrong ACLs to be served up. So make it allow anonymous access $acl = Get-Acl -Path $moduleZip $everyone = New-Object Security.Principal.SecurityIdentifier ([Security.Principal.WellKnownSidType]"WorldSid", $null) $allowAnonymous = New-Object Security.AccessControl.FileSystemAccessrule ($everyone , "ReadAndExecute","allow") $acl.AddAccessRule($allowAnonymous ) Set-Acl -Path $moduleZip -AclObject $acl Remove-Item $tempModulePath -Recurse -Force } } } Write-Progress "Creating Module Service" "Copying $($Module.name)" $moduleDir = (Split-Path $Module.Path) $moduleFiles= Get-ChildItem -Path $moduleDir -Recurse -Force | Where-Object { $dotdirs = @($_.FullName -split "\\" -like ".*") -not $_.psIsContainer -and -not $dotDirs } foreach ($moduleFile in $moduleFiles) { $moduleFile | Copy-Item -Destination { $relativePath = $_.FullName.Replace("$moduleDir\", "") $newPath = "$outputDirectory\bin\$($Module.Name)\$relativePath" $null = try { New-Item -ItemType File -Path "$outputDirectory\bin\$($Module.Name)\$relativePath" -Force } catch { # Swallowing the error from creating a new file avoids the case where a file could not be removed, # and thus a terminating error stops the pipeline $_ } Write-Progress "Copying $($req.name)" "$newPath" $newPath } -Force #-ErrorAction SilentlyContinue } $modulePath = if ($module.Path -like "*.psm1") { $module.Path.Substring(0, $module.Path.Length - ".psm1".Length) + ".psd1" } else { $module.Path } $moduleFile = [IO.Path]::GetFileName($modulePath) $importChunk = @" `$searchDirectory = if (`$request -and `$request.Params -and `$request.Params['PATH_TRANSLATED']) { `$([IO.Path]::GetDirectoryName(`$Request['PATH_TRANSLATED'])) } else { `$Request | Out-HTML return '' } `$searchOrder = @() while (`$searchDirectory) { if (-not "`$searchDirectory`") { break } Set-Location `$searchDirectory `$searchOrder += "`$searchDirectory\bin\$($Module.Name)" if (([IO.Directory]::Exists("`$searchDirectory\bin\$($Module.Name)"))) { #ImportRequiredModulesFirst Import-Module "`$searchDirectory\bin\$($Module.Name)\$moduleFile" break } `$searchDirectory = `$searchDirectory | Split-Path } "@ if ($Module.RequiredModules) { $importRequired = foreach ($req in $Module.RequiredModules) { # Make this callstack aware later $moduleDir = (Split-Path $req.Path) $moduleFiles = Get-ChildItem -Path $moduleDir -Recurse -Force | Where-Object { $dotdirs = @($_.FullName -split "\\" -like ".*") -not $_.psIsContainer -and -not $dotDirs } foreach ($moduleFile in $moduleFiles) { $moduleFile | Copy-Item -Destination { $relativePath = $_.FullName.Replace("$moduleDir\", "") $newPath = "$outputDirectory\bin\$($req.Name)\$relativePath" $null = New-Item -ItemType File -Path "$outputDirectory\bin\$($req.Name)\$relativePath" -Force Write-Progress "Copying $($req.name)" "$newPath" $newPath } -Force #-ErrorAction SilentlyContinue } $reqDir = Split-Path $req.Path "$(' ' * 8)Import-Module `"`$searchDirectory\bin\$($req.Name)\$($req.Name)`"" } $importChunk = $importChunk.Replace("#ImportRequiredModulesFirst", $importRequired -join ([Environment]::NewLine)) } $ModuleBranding = if ($pipeworksManifest.Branding) { if (-not "$($pipeworksManifest.Branding)".Trim()) { $pipeworksManifest.Branding } else { ConvertFrom-Markdown $pipeworksManifest.Branding -ShowData } } elseif ($module.CompanyName -eq 'Start-Automating') { @" <div style='text-align:right'> <div style='font-size:.75em;text-align:right;'> Provided By <a href='http://start-automating.com'> <img src='http://StartAutomating.com/Assets/StartAutomating_100_Transparent.png' alt='Start-Automating' style='vertical-align:middle;width:60px;height:60px;border:0' /> </a> </div> <div style='font-size:.75em;text-align:right;'> Powered With <a href='http://powershellpipeworks.com'> <img src='http://powershellpipeworks.com/assets/powershellpipeworks_150.png' alt='PowerShell Pipeworks' style='vertical-align:middle;width:60px;height:60px;border:0' /> </a> </div> </div> "@ } else { @" <div style='font-size:.75em;text-align:right'> Powered With <a href='http://powershellpipeworks.com'> <img src='http://powershellpipeworks.com/assets/powershellpipeworks_150.png' alt='PowerShell Pipeworks' align='middle' style='width:60px;height:60px;border:0' /> </a> </div> "@ } $initModuleDefaults = [ScriptBlock]::Create(@" if (-not `$global:PipeworksManifest) { if (`$PipeworksManifest) { `$global:PipeworksManifest = `$PipeworksManifest } else { `$global:PipeworksManifest = @{} } } if (-not `$global:PipeworksManifest.Style) { `$global:PipeworksManifest.Style = @{ Body = @{ 'Font-Family' = "'Segoe UI', 'Segoe UI Symbol', Helvetica, Arial, sans-serif" 'color' = '#0248B2' 'background-color' = '#FFFFFF' } 'a' = @{ 'color' = '#012456' } } } if (-not `$global:PipeworksManifest.Css) { `$global:PipeworksManifest.Css = @{} } if (-not `$global:SiteForegroundColor) { `$global:SiteForegroundColor = `$global:PipeworksManifest.Style.Body.Color } if (-not `$global:SiteBackgroundColor) { `$global:SiteBackgroundColor = `$global:PipeworksManifest.Style.Body.'background-color' } if (-not `$global:SiteLinkColor) { `$global:SiteLinkColor = `$global:PipeworksManifest.Style.a.Color } if (-not `$global:PipeworksManifest.ModuleTemplate ) { `$global:PipeworksManifest.ModuleTemplate = 'Module' } if (-not `$global:PipeworksManifest.CommandTemplate) { `$global:PipeworksManifest.CommandTemplate = 'Command' } if (-not `$global:PipeworksManifest.TopicTemplate) { `$global:PipeworksManifest.TopicTemplate = 'Topic' } if (-not `$global:PipeworksManifest.TemplateFiles) { `$global:PipeworksManifest.TemplateFiles = @{} Get-ChildItem "Templates" | Where-Object { `$_.Extension -eq '.pswt' -or `$_.Extension -eq '.dwt' } | Foreach-Object { if (`$_.Extension -eq '.pswt') { `$TemplateName = `$_.Name.Substring(0,`$_.Name.Length - `$_.Extension.Length) `$TemplateText = [IO.File]::ReadAllText(`$_.FullName) `$global:PipeworksManifest.TemplateFiles.`$TemplateName = `$TemplateText } } } if (-not `$global:PipeworksManifest.UseJQueryUI -and -not `$global:PipeworksManifest.UseBootstrap) { #`$global:PipeworksManifest.UseJQueryUI = `$true `$global:PipeworksManifest.Css['PipeworksCss'] = 'css/$($module).css' } if (-not `$global:PipeworksManifest.Ajax) { `$global:PipeworksManifest.NoAjax = `$true } "@) # Create the embed command. $embedCommand = $importChunk $embedCommand = $embedCommand + @" `$embedCommandStartedAt = [DateTime]::Now if (-not `$global:RealModule -or `$global:RealModule.Name -ne '$($module.Name)') { `$realModule = `$module = Get-Module `"$($module.Name)`" | Select-Object -First 1 if (-not `$module) { `$response.Write(`$searchOrder -join '<BR/>'); `$response.Flush() } `$moduleRoot = [IO.Path]::GetDirectoryName(`$module.Path) `$global:RealModule = `$Module `$global:PipeworksManifest = `$null `$script:CachedTopics = @{} `$script:CachedWalkthrus = @{} `$cmdTabs = @{} `$navBarData = @{} `$navBarUrls = @{} `$navBarOrder = @{} `$coreAboutTopic = `$null `$otherAboutTopics = `$null `$walkThrus = @{} `$aboutTopics = @() `$aboutTopicsByName = @{} `$allAboutFiles = `$null `$canCacheGroups = `$false } else { `$module = `$global:RealModule } `$moduleCommands = `$module.ExportedCommands.Values `$moduleCompany = `$module.CompanyName `$global:ModuleMaker = `$Module.CompanyName `$pipeworksManifestPath = `$moduleRoot + '\' + "`$(`$module.Name).Pipeworks.psd1" if (-not `$global:PipeworksManifest -and ([IO.File]::Exists(`$pipeworksManifestPath))) { try { `$global:pipeworksManifest = & ([ScriptBlock]::Create( "data -SupportedCommand Add-Member, New-WebPage, New-Region, Write-CSS, Write-Ajax, Out-Html, Write-Link { `$( [ScriptBlock]::Create([IO.File]::ReadAllText(`$pipeworksManifestPath)) )}")) } catch { Write-Error "Could not read pipeworks manifest" } } `$timeSpentEmbeddingCommand = [DateTime]::Now - `$embedCommandStartedAt $initModuleDefaults `$script:CachedBrandingSlot = @' $ModuleBranding '@ "@ $embedCommand = [ScriptBlock]::Create($embedCommand) $moduleRoot = (Split-Path $module.Path) if (-not $PipeworksManifest.UseJQueryUI -and -not $PipeworksManifest.UseBootstrap) { if (-not (Test-Path "$OutputDirectory\css\")) { $null= New-Item -ItemType Directory -Path "$OutputDirectory\css\" -Force } $pipeworksRoot = $MyInvocation.MYCommand.ScriptBlock.Module | Split-Path $lessCss = ([IO.File]::ReadAllText((Join-Path $pipeworksRoot "css\Pipeworks.less"))) $options = @{} if ($pipeworksManifest.style.body.'background-color'){ $options['Background'] = $pipeworksManifest.style.body.'background-color' } if ($pipeworksManifest.style.body.'color'){ $options['Foreground'] = $pipeworksManifest.style.body.'color' } if ($pipeworksManifest.style.body.'font-Family') { $options['fontFamily'] = $pipeworksManifest.style.body.'font-Family' } $myCss = Use-Less -LessCss $lessCss -Option $options [IO.File]::WriteAllText("$OutputDirectory\css\$($module).css", $myCss) } #region Check for the presence of directories, and put items within them into the manifest # Pick out all possible cultures $cultureNames = [Globalization.CultureInfo]::GetCultures([Globalization.CultureTypes]::AllCultures) | Select-Object -ExpandProperty Name $tokensInPages = New-Object Collections.ArrayList # Pages fall back on culture Write-Progress "Importing Pages" " " $pagePaths = @((Join-Path $moduleRoot "Pages")) foreach ($cultureName in $cultureNames) { if (-not $cultureName) { continue } $pagePaths+= @((Join-Path $cultureName "Pages")) } foreach ($pagePath in $pagePaths) { if (Test-Path $pagePath) { Get-ChildItem $pagePath -Recurse | Where-Object { (-not $_.PSIsContainer) -and '.htm', '.html', '.ashx','.aspx', '.jpg', '.gif', '.jpeg', '.js', '.css', '.ico', '.png', '.mpeg','.mp4', '.mp3', '.wav', '.pspage', '.docx', '.pptx', '.xlsx', '.md', '.psmd', '.pdf' '.pspg', '.ps1' -contains $_.Extension } | ForEach-Object -Process { if ($_.Extension -ne '.ps1') { # These are simple, just make the page if ($_.Extension -ne '.pspage' -and $_.Extension -ne '.html' -and $_.Extension -ne '.psmd' -and $_.Extension -ne '.md') { $bytes = try { [IO.File]::ReadAllBytes($_.Fullname) } catch { } if ($bytes) { $pipeworksManifest.Pages[$_.Fullname.Replace(($module | Split-Path), "").Replace("Pages\","").TrimStart("\")] = $bytes } } else { $text = [IO.File]::ReadAllText($_.Fullname) $pipeworksManifest.Pages[$_.Fullname.Replace(($module | Split-Path), "").Replace("Pages\","").TrimStart("\")] = $text } } else { # Embed the ps1 file contents within a <| |>, but escape the <| |> contained within $fileContents = "$([IO.File]::ReadAllText($_.Fullname))" if ($fileContents) { $sb = [ScriptBlock]::Create($fileContents) $t = [Management.Automation.PSParser]::Tokenize($sb, [ref]$null) $null = $tokensInPages.AddRange($t) } $fileContents = $fileContents.Replace("<|", "<|").Replace("|>", "|>") $pipeworksManifest.Pages[($_.Fullname.Replace(($module | Split-Path), "").Replace("Pages\","")).Replace(".ps1", ".pspage").TrimStart("\")] = "<| $fileContents |>" } } } } # Posts also fall back on culture $pagePaths = (Join-Path $moduleRoot "Posts"), (Join-Path $moduleRoot "Blog") foreach ($cultureName in $cultureNames) { if (-not $cultureName) { continue } $pagePaths+= @((Join-Path $cultureName "Blog")) $pagePaths+= @((Join-Path $cultureName "Posts")) } foreach ($pagePath in $pagePaths) { if (Test-Path $pagePath) { Get-ChildItem $pagePath | Where-Object { $_.Name -like "*.post.psd1" -or $_.Name -like "*.pspost" -or $_.Name -like "*.html" } | ForEach-Object -Begin { if (-not $PipeworksManifest.Posts) { $PipeworksManifest.Posts = @{} } } -Process { $pipeworksManifest.Posts[$_.Name.Replace(".post.psd1","").Replace(".pspost","").Replace(".html","")] = ".\$($_.Directory.Name)\$($_.Name)" } } } $jsPaths = (Join-Path $moduleRoot "JS"), (Join-Path $moduleRoot "Javascript") foreach ($cultureName in $cultureNames) { if (-not $cultureName) { continue } $jsPaths += @((Join-Path $cultureName "JS")) $jsPaths += @((Join-Path $cultureName "JavaScript")) } foreach ($jsPath in $jsPaths) { if (Test-Path $jsPath) { Get-ChildItem $jsPath | Where-Object { $_.Name -like "*.js" } | ForEach-Object -Process { if (-not $_.psiscontainer) { $pipeworksManifest.Javascript[$_.Fullname.Replace(($module | Split-Path), "")] = [IO.File]::ReadAllBytes($_.Fullname) } } } } $cssPaths = @(Join-Path $moduleRoot "CSS") foreach ($cultureName in $cultureNames) { if (-not $cultureName) { continue } $cssPaths += @((Join-Path $cultureName "CSS")) } foreach ($cssPath in $cssPaths) { if (Test-Path $cssPath) { Get-ChildItem $cssPath -Recurse | ForEach-Object -Process { if (-not $_.psiscontainer) { $pipeworksManifest.CssFile[$_.Fullname.Replace(($module | Split-Path), "")] = [IO.File]::ReadAllBytes($_.Fullname) } } } } $pipeworksRoot = Get-Module Pipeworks | Split-Path $assetPaths = (Join-Path $moduleRoot "Asset"), (Join-Path $moduleRoot "Assets"), (Join-Path $moduleRoot "Resource"), (Join-Path $moduleRoot "Resources"), (Join-Path $moduleRoot "Image"), (Join-Path $moduleRoot "Images"), (Join-Path $moduleRoot "IMG"), (Join-Path $pipeworksRoot "Template"), (Join-Path $pipeworksRoot "Templates"), (Join-Path $moduleRoot "Template"), (Join-Path $moduleRoot "Templates") foreach ($cultureName in $cultureNames) { if (-not $cultureName) { continue } $assetPaths += @((Join-Path $cultureName "Asset")) $assetPaths += @((Join-Path $cultureName "Assets")) $assetPaths += @((Join-Path $cultureName "Resource")) $assetPaths += @((Join-Path $cultureName "Resources")) $assetPaths += @((Join-Path $cultureName "Image")) $assetPaths += @((Join-Path $cultureName "Images")) $assetPaths += @((Join-Path $cultureName "IMG")) } foreach ($assetPath in $assetPaths) { if (Test-Path $assetPath ) { Get-ChildItem $assetPath -Recurse | ForEach-Object -Process { $fileInfo = $_ if (-not $_.psiscontainer) { $pipeworksManifest.AssetFile[$_.Fullname.Replace($moduleRoot, "").Replace($pipeworksRoot, "")] = [IO.File]::ReadAllBytes($_.Fullname) } } } } #endregion Check for the presence of directories, and put items within them into the manifest if ($pipeworksManifest.UseJQuery -and -not ($pipeworksManifest.'JavaScript'.Keys -like "*jquery.min.js*")) { $latestJQuery = Get-Web -Url "http://jquery.com/download/" -Tag 'a' | Where-Object {$_.Xml.Href -like "*.min.js" } | Select-Object -First 1 | ForEach-Object { $_.Xml.Href } | Get-Web -Url {$_ } -UseWebRequest -AsByte $jQueryFile = New-item -ItemType File -Path $moduleRoot\JS\jquery.min.js -Force [IO.File]::WriteAllBytes($jQueryFile.FullName, $latestJQuery) $pipeworksManifest.Javascript["JS\jquery.min.js"] = $latestJQuery } if ($PipeworksManifest.UseTableSorter -and -not ($pipeworksManifest.'JavaScript'.Keys -like "*tablesorter*")) { $tableSorterFile = New-item -ItemType File -Path $moduleRoot\JS\tablesorter.min.js -Force Get-Web http://tablesorter.com/__jquery.tablesorter.min.js | Set-Content $moduleRoot\JS\tablesorter.min.js $pipeworksManifest.Javascript["JS\tablesorter.min.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\tablesorter.min.js") } if (($pipeworksManifest.UseRaphael -or $pipeworksManifest.UseGRaphael )-and -not ($pipeworksManifest.'JavaScript'.Keys -like "*raphael*")) { $raphael = New-item -ItemType File -Path $moduleRoot\JS\raphael-min.js -Force Get-Web -Url http://raphaeljs.com/raphael.js -UseWebRequest -HideProgress | Set-Content $raphael.Fullname -Encoding UTF8 $pipeworksManifest.Javascript["JS\raphael-min.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\raphael-min.js") } if ($pipeworksManifest.UseGRaphael -and -not ($pipeworksManifest.'JavaScript'.Keys -like "*g.raphael*")) { $raphael = New-item -ItemType File -Path $moduleRoot\JS\g.raphael.js -Force Get-Web -url http://g.raphaeljs.com/g.raphael.js -UseWebRequest -HideProgress | Set-Content $raphael.Fullname $raphaelBar = New-item -ItemType File -Path $moduleRoot\JS\g.bar.js -Force Get-Web -url http://g.raphaeljs.com/g.bar.js -UseWebRequest -HideProgress | Set-Content $raphaelBar.Fullname $raphaelLine = New-item -ItemType File -Path $moduleRoot\JS\g.line.js -Force Get-Web -url http://g.raphaeljs.com/g.line.js -UseWebRequest -HideProgress | Set-Content $raphaelLine.Fullname $raphaelPie = New-item -ItemType File -Path $moduleRoot\JS\g.pie.js -Force Get-Web -url http://g.raphaeljs.com/g.pie.js -UseWebRequest -HideProgress| Set-Content $raphaelPie.Fullname $pipeworksManifest.Javascript["JS\g.raphael.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\g.raphael.js") $pipeworksManifest.Javascript["JS\g.line.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\g.line.js") $pipeworksManifest.Javascript["JS\g.pie.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\g.pie.js") $pipeworksManifest.Javascript["JS\g.bar.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\g.bar.js") } if (($pipeworksManifest.UseShiv -or $pipeworksManifest.UseShiv) -and -not ($pipeworksManifest.'JavaScript'.Keys -like "*shiv*")) { $shiv = @{ "https://github.com/aFarkas/html5shiv/blob/master/dist/html5shiv.js?raw=true" = "JS\html5shiv.js" } $pipeworksManifest.Download+=$shiv } if (($pipeworksManifest.Bootstrap -or $pipeworksManifest.UseBootstrap) -and -not ($pipeworksManifest.'JavaScript'.Keys -like "*bootstrap*")) { $textColor = if ($pipeworksManifest.Style.Body.color) { $pipeworksManifest.Style.Body.color } else { "#0248b2" } $linkColor = if ($pipeworksManifest.Style.a.color) { $pipeworksManifest.Style.a.color } else { "#0248b2" } $backgroundColor = if ($pipeworksManifest.Style.body.'background-color') { $pipeworksManifest.Style.body.'background-color' } else { "#ffffff" } $fontFamily = if ($pipeworksManifest.Style.body.'font-family') { $pipeworksManifest.Style.body.'font-family' } else { "'Segoe UI', Helvetica, Arial, sans-serif" } $fontSize = if ($pipeworksManifest.Style.body.'font-size') { $pipeworksManifest.Style.body.'font-size' } else { '15px' } $lineHeight = if ($pipeworksManifest.Style.body.'line-height') { $pipeworksManifest.Style.body.'line-height' } else { '21px' } # Download a customized bootstrap, containing their core color scheme. $r = Get-web -Url "http://bootstrap.herokuapp.com/" -Method POST -Parameter @{ js = '["bootstrap-transition.js","bootstrap-modal.js","bootstrap-dropdown.js","bootstrap-scrollspy.js","bootstrap-tab.js","bootstrap-tooltip.js","bootstrap-popover.js","bootstrap-affix.js","bootstrap-alert.js","bootstrap-button.js","bootstrap-collapse.js","bootstrap-carousel.js","bootstrap-typeahead.js"]' css= '["reset.less","scaffolding.less","grid.less","layouts.less","type.less","code.less","labels-badges.less","tables.less","forms.less","buttons.less","sprites.less","button-groups.less","navs.less","navbar.less","breadcrumbs.less","pagination.less","pager.less","thumbnails.less","alerts.less","progress-bars.less","hero-unit.less","media.less","tooltip.less","popovers.less","modals.less","dropdowns.less","accordion.less","carousel.less","wells.less","close.less","utilities.less","component-animations.less","responsive-utilities.less","responsive-767px-max.less","responsive-768px-979px.less","responsive-1200px-min.less","responsive-navbar.less"]' vars="{ `"@bodyBackground`":`"$backgroundColor`", `"@inputBackground`":`"$backgroundColor`", `"@inputText`":`"$fgColor`", `"@tableBackground`":`"$backgroundColor`", `"@heroUnitBackground`":`"$backgroundColor`", `"@heroUnitHeadingColor`":`"$textColor`", `"@heroLeadColor`":`"$textColor`", `"@infoBackground`":`"$backgroundColor`", `"@infoText`":`"$textColor`", `"@placeHolderText`":`"$textColor`", `"@headingsColor`":`"$textColor`", `"@tableBackgroundAccount`":`"$backgroundColor`", `"@tableBackgroundHover`":`"$textColor`", `"@navbarBackground`":`"$backgroundColor`", `"@navbarBackgroundHighlight`":`"$backgroundColor`", `"@navbarSearchBackground`":`"$backgroundColor`", `"@navbarLinkBackgroundActive`":`"$textColor`", `"@navbarSearchBackgroundFocus`":`"$textColor`", `"@navbarText`":`"$textColor`", `"@navbarBrandColor`":`"$textColor`", `"@navbarLinkColor`":`"$linkColor`", `"@navbarLinkColorHover`":`"$textColor`", `"@navbarLinkColorActive`":`"$textColor`", `"@dropDownBackground`":`"$backgroundColor`", `"@textColor`":`"$textColor`", `"@dropdownBackground`":`"$backgroundColor`", `"@dropdownLinkColor`":`"$linkColor`", `"@dropdownLinkColorHover`":`"$backgroundColor`", `"@dropdownLinkBackgroundHover`":`"$linkColor`", `"@btnPrimaryBackground`":`"$LinkColor`", `"@btnPrimaryBackgroundHighlight`":`"$backgroundColor`", `"@formActionsBackground`":`"$backgroundColor`", `"@linkColor`":`"$linkColor`", `"@sansFontFamily`":`"$fontFamily`", `"@monoFontFamily`":`"Menlo, Monaco, 'Consolas'`", `"@baseFontSize`":`"$FontSize`", `"@baseLineHeight`":`"$LineHeight`"}" img='["glyphicons-halflings.png","glyphicons-halflings-white.png"]' } -AsByte -UseWebRequest [IO.File]::WriteAllBytes("$moduleRoot\BootStrap.zip", $r) Expand-Zip -ZipPath "$moduleRoot\BootStrap.zip" -OutputPath "$moduleRoot" Remove-Item "$moduleRoot\bootstrap.zip" # Replace cases of the input foreground with the real text color if ($backgroundColor -eq '#555555') { Write-Warning "The specific background color of #555555 will be replaced by the textColor: $textColor, because Bootstrap's customization engine doesn't allow customization of the input text color." } $bsCss = [IO.File]::ReadAllText("$ModuleRoot\css\bootstrap.css") [IO.File]::WriteAllText("$ModuleRoot\css\bootstrap.css", $bsCss.Replace('#555555', $textColor)) $latestJQuery = Get-Web -Url "http://jquery.com/download/" -Tag 'a' | Where-Object {$_.Xml.Href -like "*.min.js" } | Select-Object -First 1 | ForEach-Object { $_.Xml.Href } | Get-Web -Url {$_ } -UseWebRequest -AsByte $jQueryFile = New-item -ItemType File -Path $moduleRoot\JS\jquery.min.js -Force [IO.File]::WriteAllBytes($jQueryFile.FullName, $latestJQuery) $pipeworksManifest.Javascript["JS\jquery.min.js"] = $latestJQuery $pipeworksManifest.Javascript["JS\bootstrap.min.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\bootstrap.min.js") $pipeworksManifest.Javascript["JS\bootstrap.js"] = [IO.File]::ReadAllBytes("$moduleRoot\JS\bootstrap.js") $pipeworksManifest.Javascript["CSS\bootstrap.css"] = [IO.File]::ReadAllBytes("$moduleRoot\CSS\bootstrap.css") $pipeworksManifest.Javascript["CSS\bootstrap.min.css"] = [IO.File]::ReadAllBytes("$moduleRoot\CSS\bootstrap.min.css") $pipeworksManifest.Javascript["IMG\glyphicons-halflings-white.png"] = [IO.File]::ReadAllBytes("$moduleRoot\IMG\glyphicons-halflings-white.png") $pipeworksManifest.Javascript["IMG\glyphicons-halflings.png"] = [IO.File]::ReadAllBytes("$moduleRoot\IMG\glyphicons-halflings.png") } #region GitIt if ($pipeworksManifest.GitIt) { if (-not $gitIt) { } foreach ($git in $pipeworksManifest.GitIt) { if ($git -is [Hashtable]) { foreach ($kv in $git.GetEnumerator()) { $gitAt = $kv.Key.Tostring().Replace("\","/") if ($gitAt -notlike "*/*/*") { Write-Error "Each key in $GitAt must include author and project" return } if ($gitAt.EndsWith('*')) { $treeUrl = "https://github.com/$($gitAt.TrimEnd('*/'))/tree" $gitHtml = Get-Web -Url $treeUrl -UseWebRequest } else { $gitBits = $gitAt -split '\/' $gitUrl = 'https://raw.github.com/' + $gitBits[0] + '/' + $gitBits[1] + '/master/' + ($gitBits[2..($gitBits.Length - 1)] -join '/') $gitTo = $kv.Value.ToString().Replace('/', '\') $downloadedfile = New-item -ItemType File -Path "${moduleRoot}/$($gitTo.Replace('\', '/').TrimStart('/'))" -Force $content = Get-Web -Url $gitUrl -UseWebRequest -AsByte if (-not $content) { Write-Error "$GitAt not on GitHub" continue } [IO.File]::WriteAllBytes($downloadedfile.Fullname, $content) $pipeworksManifest.AssetFile[$kv.Value] = [IO.File]::ReadAllBytes($downloadedfile.Fullname) } } } } } #endregion GitIt #region Download if ($pipeworksManifest.Download -and $pipeworksManifest.Download -as [Hashtable]) { foreach ($kv in $pipeworksManifest.Download.GetEnumerator()) { if (-not $kv.Key) { continue } $downloadedfile = New-item -ItemType File -Path "${moduleRoot}/$($kv.value)" -Force $content = $null $content = Get-Web -Url $kv.Key -AsByte -UseWebRequest if ($kv.key -like "*.min.js") { $null = $content } if (-not $content) { continue } [IO.File]::WriteAllBytes($downloadedfile.Fullname, $content) $pipeworksManifest.AssetFile[$kv.Value] = [IO.File]::ReadAllBytes("${moduleRoot}/$($kv.Value.ToString().Replace('\','/'))") } } #endregion Download #region Embedded Javascript, CSS, and Assets foreach ($directlyEmbeddedFileTable in 'Javascript', 'CssFile', 'AssetFile') { foreach ($fileAndData in $pipeworksManifest.$directlyEmbeddedFileTable.GetEnumerator()) { if (-not $fileAndData.Key) { continue } try { $null = New-Item "$outputDirectory\$($fileAndData.Key)" -ItemType File -Force [IO.File]::WriteAllBytes("$outputDirectory\$($fileAndData.Key)", $fileAndData.Value) } catch { $null = $_ } } } #endregion #region Object Pages if ($pipeworksManifest.ObjectPages) { foreach ($objectPageInfo in $pipeworksManifest.ObjectPages.GetEnumerator()) { $pagename = $objectPageInfo.Key $value = $objectPageInfo.Value $webOBjectPage = @" `$storageAccount = Get-WebConfigurationSetting -Setting `$pipeworksManifest.Table.StorageAccountSetting `$storageKey= Get-WebConfigurationSetting -Setting `$pipeworksManifest.Table.StorageKeySetting `$part, `$row = '$($objectPageInfo.Value.Id)' -split '\:' `$lMargin = '$marginPercentLeftString' `$rMargin = '$marginPercentRightString' `$pageName = '$($value.Title)' "@ + { if (-not $session["ObjectPage$($PageName)"]) { $session["ObjectPage$($PageName)"] = Show-WebObject -StorageAccount $storageAccount -StorageKey $storageKey -Table $pipeworksManifest.Table.Name -Part $part -Row $row | New-Region -Style @{ 'Margin-Left' = $lMargin 'Margin-Right' = $rMargin 'Margin-Top' = '2%' } | New-WebPage -Title $pageName } $session["ObjectPage$($PageName)"] | Out-HTML -WriteResponse } $pipeworksManifest.Pages["$pagename.pspage"] = "<| $webObjectPage |>" } } #endregion Object Pages #region Embed RSS Icon [IO.File]::WriteAllBytes("$outputDirectory\rss.png", [Convert]::FromBase64String(" iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABGdBTUEAAK/ INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAA JDSURBVHjajJJNSBRhGMd/887MzrQxRSLbFuYhoUhEKsMo8paHUKFLdBDrU Idunvq4RdClOq8Hb0FBSAVCUhFR1CGD/MrIJYqs1kLUXd382N356plZFOrU O/MMz/vO83+e93n+f+1zF+kQBoOQNLBJg0CTj7z/rvWjGbEOIwKp9O7Wkht Qc/wMWrlIkP8Kc1lMS8eyFHpkpo5SgWCCVO7Z5JARhuz1Qg29fh87u6/9VW L1/SPc4Qy6n8c0FehiXin6dcCQaylDMhqGz8ydS2hKkmxNkWxowWnuBLHK6 G2C8X6UJkBlxUmNqLYyNbzF74QLDrgFgh9LLE0NsPKxjW1Hz2EdPIubsOFd H2HgbwAlC4S19dT13o+3pS+vcSfvUcq9YnbwA6muW9hNpym/FWBxfh0CZkK GkPBZeJFhcWQAu6EN52QGZ/8prEKW+cdXq0039UiLXhUYzdjebOJQQI30UX p6mZn+Dtam32Afu0iyrgUvN0r+ZQbr8HncSpUVJfwRhBWC0hyGV8CxXBL5S WYf9sYBidYLIG2V87/ifVjTWAX6AlxeK2C0X8e58hOr/Qa2XJ3iLMWxB1h7 2tHs7bgryzHAN2o2gJorTrLxRHVazd0o4TXiyV2Yjs90uzauGvvppmqcLjw mbZ3V7BO2HOrBnbgrQRqWUgTZ5+Snx4WeKfzCCrmb3axODKNH+vvUyWjqyK 4DiKQ0eXSpFsgVvLJQWpH+xSpr4otg/HI0TR/t97cxTUS+QxIMRTLi/9ZYJ PI/AgwAoc3W7ZrqR2IAAAAASUVORK5CYII=")) #endregion Embed RSS Icon #region HTML Based Blog # The value of the post field can either be a hashtable containing these items, or a relative path to a .post.psd1 containing # these items. $hasPosts = $false if ($PipeworksManifest.Posts -and $PipeworksManifest.Posts.GetType() -eq [Hashtable] ) { if (-not $hasPosts) { } $hasPosts = $true $getPostFileNames = { param($post) $replacedPostTitle = $post.Title.Replace("|", " ").Replace("/", "-").Replace("\","-").Replace(":","-").Replace("!", "-").Replace(";", "-").Replace(" ", "_").Replace("@","at").Replace(",", " ") New-Object PSObject -Property @{ safeFileName = $replacedPostTitle + ".simple.html" postFileName = $replacedPostTitle + ".post.html" postDirectory = $replacedPostTitle postRssFileName = $replacedPostTitle + ".xml" datePublishedFileName = try { ([DateTime]($post.DatePublished)).ToString("u").Replace(" ", "_").Replace(":", "-") + ".simple.html"} catch {} } } # Get the command now so we can remove anything else from the pagecontent hashtable later $rssItem = Get-Command New-RssItem | Select-Object -First 1 $moduleRssName = $moduleBlogTitle.Replace("|", " ").Replace("/", "-").Replace("\","-").Replace(":","-").Replace("!", "-").Replace(";", "-").Replace(" ", "_").Replace("@","at").Replace(",", "_") $allPosts = foreach ($postAndContent in $PipeworksManifest.Posts.GetEnumerator()) { $pageName = $postAndContent.Key $pageContent = $postAndContent.Value $safePageName = $pageName.Replace("|", " ").Replace("/", "-").Replace("\","-").Replace(":","-").Replace("!", "-").Replace(";", "-").Replace(" ", "_").Replace("@","at").Replace(",", "_") if ($pageContent -like ".\*") { # Relative Path, try loading a post file $pagePath = Join-Path $moduleRoot $pageContent.Substring(2) if ($pagePath -notlike "*.htm*" -and (Test-Path $pagePath)) { try { $potentialPagecontent = [IO.File]::ReadAllText($pagePath) $potentialPagecontentAsScriptBlock = [ScriptBlock]::Create($potentialPageContent) $potentialPagecontentAsDataScriptBlock = [ScriptBlock]::Create("data { $potentialPagecontentAsScriptBlock }") $pageContent = & $potentialPagecontentAsDataScriptBlock } catch { $_ | Write-Error } } elseif (Test-Path $pagePath) { # Page is HTML. $pageContent = [IO.File]::ReadAllText($pagePath) # Try quickly to get the microdata from the HTML. $foundMicroData = Get-Web -Html $pageContent -ItemType http://schema.org/BlogPosting -ErrorAction SilentlyContinue | Select-Object -First 1 if ($foundMicrodata) { $pageContent = @{ Title = $foundMicrodata.Name Description = $foundMicrodata.ArticleText DatePublished = $foundMicrodata.DatePublished Category = $foundMicrodata.Keyword Author = $foundMicrodata.author Link = $foundMicrodata.url } } } } $feedContent = if ($pageContent -is [Hashtable]) { if (-not $pageContent.Description -and -not $pageContent.html) { continue } if ($pageContent.Html) { $pageContent.Description = $pageContent.html } if (-not $pageContent.Title) { $pageContent.Title = $pageName } foreach ($key in @($pageContent.Keys)) { if ($rssItem.Parameters.Keys -notcontains $key) { $pageContent.Remove($key) } } $fileNames = & $getPostFileNames $pageContent $pageContent.Link = $filenames.postFileName New-RssItem @pageContent } else { Write-Debug "$safePageName could not be processed" continue } $safePageName = $fileNames.postFileName $xmlDescription = $pageContent.Description if (-not $pageContent.Description) { continue } $feedContent | Out-RssFeed -Title $pageName -Description $xmlDescription -Link "${safePageName}.Post.xml"| Set-Content "$outputDirectory\${safePageName}.Post.xml" $pageWidgetContent = $ExecutionContext.SessionState.InvokeCommand.ExpandString($blogPostTemplate) $feedHtml = New-RssItem @pageContent -AsHtml # Create an article page New-WebPage -UseJQueryUI -Title $pageContent.Title -PageBody ( New-Region -Container 'Headerbar' -Border '0px' -Style @{ "margin-top"="1%" 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -Content " <h1 class='blogTitle'><a href='$moduleBlogLink'>$moduleBlogTitle</a></h1> <h4 class='blogDescription'>$moduleBlogDescription</h4>"), ( New-Region -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' } -AsWidget -ItemType http://schema.org/BlogPosting -Content $feedHtml ) | Set-Content "$outputDirectory\${safePageName}" -PassThru | Set-Content "$outputDirectory\$($safePageName.Replace('.post.html', '.html'))" # Emit the page content, so the whole feed can be generated $feedContent } if ($allPosts) { $moduleRss = $allPosts | Out-RssFeed -Title $moduleBlogTitle -Description $module.Description -Link "\$($module.Name).xml" | Set-Content "$outputDirectory\$moduleRssName.xml" -PassThru | Set-Content "$outputDirectory\Rss.xml" -PassThru } else { $moduleRss = @() } $categories = $moduleRss | Select-Xml //item/category | Group-Object { $_.Node.'#text'} $postsByYear = $moduleRss | Select-Xml //item/pubDate | Group-Object { ([DateTime]$_.Node.'#text').Year } $postsByYearAndMonth = $moduleRss | Select-Xml //item/pubDate | Group-Object { ([DateTime]$_.Node.'#text').ToString("y") } $allGroups = @($categories) + $postsByYear + $postsByYearAndMonth foreach ($groupPage in $allGroups) { if (-not $groupPage) { continue } $catLink = $groupPage.Name.Replace("|", " ").Replace("/", "-").Replace("\","-").Replace(":","-").Replace("!", "-").Replace(";", "-").Replace(" ", "_").Replace("@","at").Replace(",", "_") + ".posts.html" $groupPage.Group | ForEach-Object { $_.Node.SelectSingleNode("..") } | Sort-Object -Descending { ([datetime]$_.pubdate).'#text' } | Select-Object title, creator, pubdate, link, category, @{ Name='Description'; Expression={ $_.Description.InnerXml.Substring("<![CDATA[".Length).TrimEnd("]]>") } } | New-RssItem -AsHTML | New-Region -ItemType http://schema.org/BlogPosting -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' }| ForEach-Object -Begin { New-Region -Container 'Headerbar' -Border '0px' -Style @{ "margin-top"="1%" 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -Content " <h1 class='blogTitle'><a href='$moduleBlogLink'>$moduleBlogTitle</a></h1> <p class='blogDescription'>$moduleBlogDescription</p> <h2 class='blogCategoryHeader' style='text-align:right'>$($groupPage.Name)</h2> " } -Process { $_ } | New-WebPage -Title "$moduleBlogTitle - $($groupPage.Name)" -Rss @{"Start-Scripting"= "$moduleRssName.xml"} | Set-Content "$outputDirectory/$catLink" } } #endregion HTML Based Blog $embedUnpackItem = "`$unpackItem = {$unpackItem }" # This seems counter-intuitive, and so bears a little explanation. # This makes schematics have a natural priority order according to how they were specified # That is, if you have multiple schematics, you want the first item to be the most important # (and it's default page to be the default page). If it was processed first, this wouldn't happen. # If this was sorted, also no. So, it's flipped. if ($psboundParameters.useSchematic) { $useSchematic = $useSchematic[-1..(0 -$useSchematic.Length)] } foreach ($schematic in $useSchematic) { $moduleList = (@($realModule) + @($module.RequiredModules) + @(Get-Module Pipeworks)) $moduleList = $moduleList | Select-Object -Unique foreach ($moduleInfo in $moduleList ) { $thisModuleDir = $moduleInfo | Split-Path $schematics = "$thisModuleDir\Schematics\$Schematic\" | Get-ChildItem -Filter "Use-*Schematic.ps1" -ErrorAction SilentlyContinue foreach ($s in $schematics) { if (-not $s) { continue } if (-not $pipeworksManifest.$Schematic) { Write-Error "Missing $schematic schematic parameters for $($module.Name)" continue } $pagesToMerge = & { . $s.Fullname $schematicCmd = Get-Command -Verb Use -Noun *Schematic | Where-Object {$_.Name -ne 'Use-Schematic'} | Select-Object -First 1 $schematicParameters = @{ Parameter = $pipeworksManifest.$schematic Manifest = $PipeworksManifest DeploymentDirectory = $outputDirectory inputDirectory = $moduleRoot } if ($schematicCmd.Name) { & $schematicCmd @schematicParameters Remove-Item "function:\$($schematicCmd.Name)" } } if ($pagesToMerge) { foreach ($kv in $pagesToMerge.GetEnumerator()) { $pipeworksManifest.pages[$kv.Key] = $kv.Value } } } } } if ($pipeworksManifest.Table) { $RequiresPipeworks = $module.RequiredModules | Where-Object { $_.Name -eq 'Pipeworks'} if (-not $requiresPipeworks -and ($module.Name -ne 'Pipeworks')) { Write-Error "Modules that use the Pipeworks Manifest table features must require Pipeworks in the module manifest. Please add RequiredModules='Pipeworks' to the module manifest.'" return } if ($PipeworksManifest.Table.StorageAccountSetting) { $storageAccount = $configSetting[$PipeworksManifest.Table.StorageAccountSetting] } if ($PipeworksManifest.Table.StorageKeySetting) { $storageKey = $configSetting[$PipeworksManifest.Table.StorageKeySetting] } } if ($hasPosts -and $ModuleRss) { # Generate the main page, which is an expanded first item with popouts linking to other items $moduleRss | Select-Xml //item/pubDate | Sort-Object -Descending { ([DateTime]$_.Node.'#text') } | Select-Object -First 1 | ForEach-Object { $_.Node.SelectSingleNode("..") } | Select-Object title, creator, pubdate, link, category, @{ Name='Description'; Expression={ $_.Description.InnerXml.Substring("<![CDATA[".Length).TrimEnd("]]>") } } | New-RssItem -AsHTML | New-Region -ItemType http://schema.org/BlogPosting -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' }| ForEach-Object -Begin { New-Region -Container 'Headerbar' -Border '0px' -Style @{ "margin-top"="1%" 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -Content " <h1 class='blogTitle'><a href='$moduleBlogLink'>$moduleBlogTitle</a></h1> <p class='blogDescription'>$moduleBlogDescription</p> " } -Process { $_ } | New-WebPage -Title "$moduleBlogTitle" -Rss @{"$moduleBlogTitle"= "$moduleRssName.xml"} | Set-Content "$outputDirectory/Blog.html" } $usesDynamicPages = $false if ($AllowDownload) { # Generate the download page now, so the site can be baked and so that we don't waste time rending the page if ($downloadUrl) { $page = New-WebPage -Title "Download $($module.Name)" -RedirectTo "$downloadUrl" $pipeworksManifest.Pages["Download.html"] = $page } elseif ($allowDownload) { $modulezip = $module.name + '.' + $module.Version + '.zip' $page = (New-object PSObject -Property @{RedirectTo=$modulezip;RedirectIn='0:0:0.50'}),(New-object PSObject -Property @{RedirectTo="./";RedirectIn='0:0:5'}) | New-WebPage $pipeworksManifest.Pages["Download.html"] = $page } } #region Pages #If the manifest declares additional web pages, create a page for each item if ($PipeworksManifest.Pages -and $PipeworksManifest.Pages.GetType() -eq [Hashtable] ) { $pageCounter = 0 foreach ($pageAndContent in $PipeworksManifest.Pages.GetEnumerator()) { $pageName = $pageAndContent.Key $pageCounter++ $pagePercent = $pageCounter * 100 / $pipeworksManifest.Pages.Count Write-Progress "Creating Pages" "$pageName" -PercentComplete $pagePercent $safePageName = $pageName.Replace("|", " ").Replace("/", "-").Replace(":","-").Replace("!", "-").Replace(";", "-").Replace(" ", "_").Replace("@","at") $pageContent = $pageAndContent.Value $realPageContent = if ($pageContent -is [Hashtable]) { if (-not $pageContent.Css -and $pipeworksManifest.Style) { $pageContent.Css = $pipeworksManifest.Style } if ($pageContent.PageContent) { $pageContent.PageContent = try { [ScriptBlock]::Create($pageContent.PageContent) } catch {} } if ($hasPosts) { # If there are posts, add a link to the feed to all pages $pageContent.Rss = @{ "$($Module.Name) Blog" = "$($module.Name).xml" } } # Pass down the analytics ID to the page if one is not explicitly set if (-not $pageContent.AnalyticsId -and $analyticsId) { $pageContent.AnalyticsId = $analyticsId } New-WebPage @pageContent } elseif ($pageContent -like ".\*.pspg" -or $pageName -like "*.pspg" -or $pageName -like "*.pspage"){ # .PSPages. These are mixed syntax HTML and Powershell inlined in markup <| |> # Because they are loaded within the moudule, a PSPAge will contain $embedCommand, which imports the module if ($pageContent -notlike ".\*.pspg" -and $pageContent -notlike ".\*.pspage") { # the content isn't a filepath, so treat it as inline code $wholePageContent = "<| $embedCommand |>" + $pageContent ConvertFrom-InlinePowerShell -PowerShellAndHtml $wholePageContent -RunScriptMethod this.RunScript -CodeFile PowerShellPageBase.cs -Inherit PowerShellPage | Add-Member NoteProperty IsPsPage $true -PassThru } else { # The content is a path, treat it like one $pagePath = Join-Path $moduleRoot $pageContent.TrimStart(".\") if (Test-Path $pagePath) { $pageContent = [IO.File]::ReadAllText($pagePath) $wholePageContent = "<| $embedCommand |>" + $pageContent ConvertFrom-InlinePowerShell -PowerShellAndHtml $wholePageContent -CodeFile PowerShellPageBase.cs -Inherit PowerShellPage -RunScriptMethod this.RunScript | Add-Member NoteProperty IsPsPage $true -PassThru } } $usesDynamicPages= $true } elseif ($pageName -like "*.*" -and $pageContent -as [Byte[]]) { # Path to item $itemPath = Join-Path $outputDirectory $pageName.TrimStart(".\") $parentPath = $itemPath | Split-Path if (-not (Test-Path "$parentPath")) { $null = New-Item -ItemType Directory -Path "$parentPath" } [IO.File]::WriteAllBytes("$itemPath", $pageContent) } elseif ($pageContent -like ".\*.htm*" -or $pagename -like "*.htm?"){ # .HTML files $pagePath = Join-Path (Join-Path $moduleRoot "Pages") $pageName.TrimStart(".\") if (Test-Path $pagePath) { try { $pageItem =(Get-Item $pagePath) $potentialPagecontent = [IO.File]::ReadAllText($pageItem.FullName) # Include the page header information for each page $relativepath = $pageItem.Fullname -ireplace $moduleRoot.Replace(".", "\.").Replace("\","\\"),"" -ireplace "\\Pages\\", "" $depth = @($relativepath -split "\\").Count - 1 $pageHeaderHtml = (& { $null = . New-WebPage -Depth $depth; $pageHeaderHtml }) if (-not $allAboutFiles) { $allAboutFiles = @{} foreach ($cult in ([Globalization.CultureInfo]::GetCultures([Globalization.CultureTypes]::AllCultures))) { $allAboutFiles[$cult.Name] = @(Get-ChildItem -Filter *.help.txt -Path "$moduleRoot\$($cult.Name)" -ErrorAction SilentlyContinue) } } $headStart = $potentialPagecontent.IndexOf("<head>", [StringComparison]::InvariantCultureIgnoreCase) $potentialPagecontent = $potentialPagecontent.Insert(($headStart + "<head>".Length), $pageHeaderHtml) if ($pageItem.Directory.Name -eq 'Templates' -or $pageItem.Directory.Name -eq 'Template') { $regions = Get-WebTemplateEditableRegion -FilePath $pageItem.FullName $moduleParts = 'docTypeText','Title','pageHeaderHtml','titleArea', 'descriptionArea', 'rssLink','socialArea','navBarHtml', 'confirmationArea','upperBannerSlot', 'topicHtml', 'defaultCommandSection', 'slideShowHtml', 'rest', 'bottomBannerSlot','orgArea', 'brandingSlot' $commandParts = $topicParts = 'docTypeText','Title','pageHeaderHtml','titleArea', 'descriptionArea', 'rssLink','socialArea','navBarHtml', 'confirmationArea','upperBannerSlot', 'rest', 'bottomBannerSlot','orgArea', 'brandingSlot' if ($pageItem.Name -like "Module*") { $difference = $regions.Region | Compare-Object $moduleParts } elseif ($PageItem.Name) { $difference = $regions.Region | Compare-Object $commandParts } foreach ($rinf in $regions.MatchInfo) { $null =$rinf $start = $potentialPagecontent.IndexOf($rinf.Value) $end = $potentialPagecontent.IndexOf("<!-- #EndEditable -->", $start, [StringComparison]::InvariantCultureIgnoreCase) $part = $potentialPagecontent.Substring($start, $end-$start + "<!-- #EndEditable -->".Length) $potentialPagecontent = $potentialPagecontent.Replace($part, '$' + $rinf.Groups[1]) $pagename = $pagename -iReplace "\.html", ".pswt" -iReplace "\.htm",".pswt" } } # 9/16/2013 - Adding support for embedded topics and commands in HTML # If any HTML file contains a variable (i.e. $foo) that matches the name # of an about topic, walkthru, or command, then the item will be embedded in the HTML. # Topics and Walkthrus will be directly embedded # Commands will be embedded with Write-Ajax. # Unmatched items will be unchanged, but will warn the user $variableMatches = New-Object Collections.ArrayList $fn = $pageItem.Fullname $m = [Regex]::Matches($potentialPagecontent, "\`$([\w-]{1,})") | Add-Member NoteProperty FileName $fn -Force -PassThru $variablesUsed = if ($m) { foreach ($ma in $m) { if (-not ($ma.Value -as [double])) { $ma.Value $null = $variableMatches.Add($ma) } } } $variablesUsed = $variablesUsed | Select-Object -Unique | Sort-Object -Descending $foundVariables = @() $topicsToReplace = $allAboutFiles.Values | where-object { $_ } | Where-Object { $variablesUsed -like "?$($_.Name -ireplace '\.walkthru\.help\.txt', '' -ireplace '\.help\.txt', '')" } | ForEach-Object { foreach ($f in $_) { $used = $($variablesUsed -like "?$($_.Name -ireplace '\.walkthru\.help\.txt', '' -ireplace '\.help\.txt', '')") New-Object PSObject -Property @{ TopicFile = $f UsedInFile = $variableMatches | Where-Object { $used -contains $_.Value } | Select-Object -ExpandProperty Filename Variable = $used Matches = $variableMatches | Where-Object { $used -contains $_.Value } } } $foundVariables += $used } $moduleCommands = Get-Command -Module $Name -CommandType Alias, Function, Cmdlet $commandsUsed = foreach ($m in $moduleCommands) { $m | Where-Object { $variablesUsed -like "?$($_.Name -ireplace '\.walkthru\.help\.txt', '' -ireplace '\.help\.txt', '')" } | ForEach-Object { $used = $($variablesUsed -like "?$($_.Name -ireplace '\.walkthru\.help\.txt', '' -ireplace '\.help\.txt', '')") New-Object PSObject -Property @{ Command = $m UsedInFile = $variableMatches | Where-Object { $used -contains $_.Value } | Select-Object -ExpandProperty Filename Variable = $used Matches = $variableMatches | Where-Object { $used -contains $_.Value } } $foundVariables+=$used } } foreach ($cmd in $commandsUsed) { foreach ($matchInf in $cmd.Matches) { $relativepath = $matchInf.Filename -ireplace $moduleRoot.Replace(".", "\.").Replace("\","\\"),"" -ireplace "\\Pages\\", "" $resolvedCommand = if ($cmd.Command.ResolvedCommand) { $cmd.Command.ResolvedCommand } else { $cmd.Command } $depth = @($relativepath -split "\\").Count - 1 $cmdLink = "${RelativeDepth}${resolvedCommand}/?snug=true&ajax=true" $fc = [IO.File]::ReadAllText($matchInf.FileName) $cmdIFrame = "$($resolvedCommand.Name.Replace('-',''))frame" $cmdHtml = "<iframe style='width:100%;border:0' src='$cmdLink' id='$cmdIFrame' seamless=''></iframe>" $cmdHtml = " <div style='height:100%'> <div id='$($resolvedCommand.Name.Replace("-", ''))'> </div> $cmdHtml </div> <script> var buffer = 20; //scroll bar buffer var iframe = document.getElementById('$cmdIFrame'); function pageY(elem) { return elem.offsetParent ? (elem.offsetTop + pageY(elem.offsetParent)) : elem.offsetTop; } function resize$cmdIFrame() { var height = document.documentElement.clientHeight; height -= pageY(document.getElementById('$cmdIFrame'))+ buffer ; height = (height < 0) ? 0 : height; document.getElementById('$cmdIFrame').style.height = height + 'px'; } // .onload doesn't work with IE8 and older. if (iframe.attachEvent) { iframe.attachEvent('onload', resize$cmdIFrame); } else { iframe.onload=resize$cmdIFrame; } if (window.addEventListener) { window.addEventListener('onresize', function() { resize$cmdIFrame(); }); window.addEventListener('onorientationchange', function() { resize$cmdIFrame(); }); } else { if (window.attachEvent) { window.attachEvent('onresize', function(e) { resize$cmdIFrame(); }); } } </script> " $potentialPagecontent = [Regex]::Replace($potentialPagecontent, $matchInf.Value.Replace('$', '\$'), $cmdHtml) } } $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } foreach ($topic in $topicsToReplace) { $topicHtml = if ($topic.TopicFile -like "*.walkthru*") { Write-WalkthruHTML -WalkThru (Get-Walkthru -File $topic.TopicFile) } else { $topicHelp = [IO.File]::ReadAllText($TOPIC.TopicFile.FullName) ConvertFrom-Markdown -Markdown $topicHelp -ShowData:$ShowDataInTopic } $matchInf = $topic.Matches foreach ($matchInf in $topic.Matches) { $potentialPagecontent = [Regex]::Replace($potentialPagecontent, $matchInf.Value.Replace('$', '\$'), $topicHtml) } } $missingvariables = $variablesUsed | Where-Object { $foundVariables -notcontains $_ } foreach ($missing in $missingvariables) { Write-Warning "$($pagePath) references $missing, but there is no topic, walkthru, or command named $missing" } $pageContent = $potentialPagecontent } catch { $_ | Write-Error } } } elseif ($pageName -like ".\*.md" -or $pagename -like "*.md") { $pagePath = Join-Path (Join-Path $moduleRoot "Pages") $pageName.TrimStart(".\") if (Test-Path $pagePath) { try { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } $potentialPagecontent = [IO.File]::ReadAllText((Get-Item $pagePath).Fullname) $wholePageContent = " <| $embedCommand ConvertFrom-Markdown -ShowData:`$$showDataInTopic -Markdown @`" $potentialPagecontent `"@ | New-Webpage |> " ConvertFrom-InlinePowerShell -PowerShellAndHtml $wholePageContent -CodeFile PowerShellPageBase.cs -Inherit PowerShellPage -RunScriptMethod this.RunScript | Add-Member NoteProperty IsPsPage $true -PassThru } catch { $_ | Write-Error } } } elseif ($pageName -like ".\*.psmd" -or $pagename -like "*.psmd") { $pagePath = Join-Path (Join-Path $moduleRoot "Pages") $pageName.TrimStart(".\") if (Test-Path $pagePath) { try { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } $potentialPagecontent = [IO.File]::ReadAllText((Get-Item $pagePath).Fullname) $wholePageContent = " <| $embedCommand ConvertFrom-Markdown -Splat -ScriptAsPowershell -ShowData:`$showDataInTopic -Markdown @`" $potentialPagecontent `"@ | New-Webpage |> " ConvertFrom-InlinePowerShell -PowerShellAndHtml $wholePageContent -CodeFile PowerShellPageBase.cs -Inherit PowerShellPage -RunScriptMethod this.RunScript | Add-Member NoteProperty IsPsPage $true -PassThru } catch { $_ | Write-Error } $usesDynamicPages = $true } } else { $pageContentAsScriptBlock = try { [ScriptBlock]::Create($pageContent) } catch { } if ($pageContentAsScriptBlock) { & $pageContentAsScriptBlock } else { $pageContent } } if ($realPageContent.IsPsPage) { $safePageName = $safePageName.Replace(".pspage", "").Replace(".pspg", "").Replace(".md", "").Replace(".psmd", "") $parentPath = $safePageName | Split-Path if (-not (Test-Path "$outputDirectory\$parentPath")) { $null = New-Item -ItemType Directory -Path "$outputDirectory\$parentPath" } $realPageContent | Set-Content "$outputDirectory\${safepageName}.aspx" $usesDynamicPages = $true } else { # Output the bytes $parentPath = $safePageName | Split-Path if (-not (Test-Path "$outputDirectory\$parentPath")) { $null = New-Item -ItemType Directory -Path "$outputDirectory\$parentPath" } if ($pageContent -as [Byte[]]) { [IO.File]::WriteAllBytes("$outputDirectory\$($pageName)", $pageContent) } else { [IO.File]::WriteAllText("$outputDirectory\$($pageName)", $pageContent) } <# $safePageName = $safePageName.Replace(".html", "").Replace(".htm", "") $parentPath = $safePageName | Split-Path if (-not (Test-Path "$outputDirectory\$parentPath")) { $null = New-Item -ItemType Directory -Path "$outputDirectory\$parentPath" } $realPageContent | Set-Content "$outputDirectory\${safepageName}.html" #> } } } #endregion Pages #region Command Handlers $webCmds = @() $downloadableCmds = @() $cmdOutputDirs = @() foreach ($command in $module.ExportedCommands.Values) { # Generate individual handlers $extraParams = if ($pipeworksManifest -and $pipeworksManifest.WebCommand.($Command.Name)) { @{} + $pipeworksManifest.WebCommand.($Command.Name) } else { @{ ShowHelp = $true } } if ($pipeworksManifest -and $pipeworksManifest.Style -and (-not $extraParams.Style)) { $extraParams.Style = $pipeworksManifest.Style } if ($extraParams.Count -gt 1) { # Very explicitly make sure it's there, and not explicitly false if (-not $extra.RunOnline -or $extraParams.Contains("RunOnline") -and $extaParams.RunOnline -ne $false) { $extraParams.RunOnline = $true } } if ($extaParams.PipeInto) { $extaParams.RunInSandbox = $true } if (-not $extraParams.AllowDownload) { $extraParams.AllowDownload = $allowDownload } if ($extraParams.RunOnline) { # Commands that can be run online $webCmds += $command.Name } if ($extraParams.RequireAppKey -or $extraParams.RequireLogin -or $extraParams.IfLoggedAs -or $extraParams.ValidUserPartition) { $extraParams.UserTable = $pipeworksManifest.Usertable.Name $extraParams.UserPartition = $pipeworksManifest.Usertable.Partition $extraParams.StorageAccountSetting = $pipeworksManifest.Usertable.StorageAccountSetting $extraParams.StorageKeySetting = $pipeworksManifest.Usertable.StorageKeySetting } if ($extraParams.AllowDownload) { # Downloadable Commands $downloadableCommands += $command.Name } if ($psBoundParameters.OutputDirectory) { $extraParams.OutputDirectory = Join-Path $psBoundParameters.OutputDirectory $command.Name $cmdOutputDirs += "$(Join-Path $psBoundParameters.OutputDirectory $command.Name)" } else { $extraParams.OutputDirectory = Join-Path $OutputDirectory $command.Name } if ($MarginPercentLeftString -and (-not $extraParams.MarginPercentLeft)) { $extraParams.MarginPercentLeft = $MarginPercentLeftString.TrimEnd("%") } if ($MarginPercentRightString-and -not $extraParams.MarginPercentRight) { $extraParams.MarginPercentRight = $MarginPercentRightString.TrimEnd("%") } if ($IsolateRunspace) { $extraParams.IsolateRunspace = $IsolateRunspace } if ($psBoundParameters.StartOnCommand) { # only create a full command service when the Module service starts on a command #ConvertTo-CommandService -Command $command @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" } $cmdOutputDir = $extraParams.OutputDirectory.ToString() } #endregion Command Handlers foreach ($cmdOutputDir in $cmdOutputDirs) { } if (-not $CommandOrder) { if ($pipeworksManifest.CommandOrder) { $CommandOrder = $pipeworksManifest.CommandOrder } else { $CommandOrder = $module.ExportedCommmands.Keys | Sort-Object } } $useLoginHandlers = foreach ($wc in $pipeworksManifest.WebCommand.Values) { if ($wc.RequireLogin -or $wc.RequiresLogin -or $wc.IfLoggedInAS -or $wc.ValidUserTable -or $wc.RequireAppKey -or $wc.RequiresAppKey) { $true break } } # This script is embedded in the module handler $getModuleMetaData = { $startedGetModuleMetaDataAt = [DateTime]::Now # $moduleRoot = [IO.Path]::GetDirectoryName($module.Path) $psd1Path = $moduleRoot + '\' + $module.Name + '.psd1' $requestCulture = if ($request -and $request["HTTP_ACCEPT_LANGUAGE"]) { $request["HTTP_ACCEPT_LANGUAGE"] } else { "en-us" } $namedTopics = @{} if (-not $customAnyHandler) { $customAnyHandler = [IO.File]::Exists("$searchDirectory\AnyUrl.aspx") } $spacingDiv = "<div style='clear:both;margin-top:1.5%;margin-bottom:1.5%'></div>" if (-not $aboutFiles) { if (-not $allAboutFiles) { $allAboutFiles = @{} foreach ($cult in ([Globalization.CultureInfo]::GetCultures([Globalization.CultureTypes]::AllCultures))) { $allAboutFiles[$cult.Name] = @(Get-ChildItem -Filter *.help.txt -Path "$moduleRoot\$($cult.Name)" -ErrorAction SilentlyContinue) } } if (-not $cachedAboutTopics) { $cachedAboutTopics = @{} } # en-us and the current request culture get are used to create a list of help topics $aboutFiles = @($AllaboutFiles["en-us"]) if ($requestCulture -and ($requestCulture -ine 'en-us')) { $aboutFiles += @($AllaboutFiles["$requestCulture"]) } if (-not $walkThrus) { $walkThrus = @{} } if (-not $aboutTopics) { $aboutTopics = @() } if (-not $aboutTopicsByName) { $aboutTopicsByName = @{} } $HiddenTopicsByName = @{} $hiddenTopic = if ($pipeworksManifest.HiddenTopic) { @($pipeworksManifest.HiddenTopic) } elseif ($pipeworksManifest.HiddenTopics) { @($pipeworksManifest.HiddenTopics) } $memberTopic = if ($pipeworksManifest.MemberTopic) { @($pipeworksManifest.MemberTopic) } elseif ($pipeworksManifest.MemberTopics) { @($pipeworksManifest.MemberTopics) } foreach ($topic in $aboutFiles) { if (-not $topic) { continue } if (-not $topic.Name) { continue } if ($topic.fullname -ilike "*.walkthru.help.txt") { $topicName = $topic.Name.Replace('_',' ') -iReplace '\.walkthru\.help\.txt','' if (-not $walkThrus[$topicName]) { $walkthruContent = Get-Walkthru -File $topic.Fullname $walkThrus[$topicName] = $walkthruContent } } else { $topicName = $topic.Name.Replace(".help.txt","") if (-not $aboutTopicsByName[$topicName.Replace("_", " ")]) { $nat = New-Object PSObject -Property @{ Name = $topicName.Replace("_", " ") SystemName = $topicName Topic = [IO.File]::ReadAllText($topic.Fullname) LastWriteTime = $topic.LastWriteTime } $aboutTopics += $nat $aboutTopicsByName[$nat.Name] = $nat } else { $aboutTopics += $aboutTopicsByName[$nat.Name] } } } } $timeSpentGettingModuleMetaData = [DateTime]::now - $startedGetModuleMetaDataAt } $moduleFeedHandler = { if ($application -and $application["$($module.Name)_Feed"]) { $response.ContentType = "text/xml" $response.Write("$($application["$($module.Name)_Feed"])") return } $feedItems = @() if ($aboutTopics) { $feedItems += $aboutTopics | Select-Object @{ Name = 'Author' Expression = { if ($module.Author) { $module.Author } else { " " } } }, @{ Name = 'Title' Expression = { $_.Name } }, @{ Name = 'Description' Expression = { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } ConvertFrom-Markdown -Markdown "$($_.Topic) " -ScriptAsPowerShell -ShowData:$ShowDataInTopic } }, @{ Name = 'DatePublished' Expression = { $_.LastWriteTime } }, @{ Name = 'Link' Expression = { if ($customAnyHandler) { "?About=" + $_.Name } else { $_.Name + "/" } } } | Where-Object { $_.Title -ne "About $Module" } } if ($walkThrus) { $feedItems += $walkThrus.Keys | Select-Object @{ Name = 'Author' Expression = { if ($module.Author) { $module.Author } else { " " } } }, @{ Name = 'Title' Expression = { $_ } }, @{ Name = 'Description' Expression = { Write-WalkthruHTML -WalkThru ($walkThrus[$_]) } }, @{ Name = 'DatePublished' Expression = { $walkThrus[$_] | Select-Object -ExpandProperty LastWriteTime -Unique } }, @{ Name = 'Link' Expression = { if ($customAnyHandler) { "?Walkthru=" + $_ } else { $_ + "/" } } } } $feedName = if ($pipeworksManifest.Blog.Name) { $pipeworksManifest.Blog.Name } else { $module.Name } $feedDescription = if ($pipeworksManifest.Blog.Description) { $pipeworksManifest.Blog.Description } else { $module.Description } $postFilters = @($pipeworksManifest.Blog.Posts) + @($pipeworksManifest.Blog.Post) if ($postFilters) { $feedItems = @(foreach ($fi in $feedItems) { $ItemIsOk = $false foreach ($po in $postFilters) { if ($fi.Name -like $po) { $ItemIsOk = $true break } } if ($ItemIsOk) { $fi } }) } $feed = $feedItems | Sort-Object { $_.DatePublished -as [DateTime] } -Descending | New-RssItem -Description { "$($_.Description) " }| Out-RssFeed -Title "$feedname " -Description "$feedDescription " -Link "/" $response.ContentType = "text/xml" $strWrite = New-Object IO.StringWriter ([xml]($feed)).Save($strWrite) $resultToOutput = "$strWrite" -replace "encoding=`"utf-16`"", "encoding=`"utf-8`"" $application["$($module.Name)_Feed"] = "$resultToOutput" $response.Write("$resultToOutput") } $topicRssHandler = { if ($aboutTopics) { $feed = $aboutTopics | New-RssItem -Author { if ($module.Author) { $module.Author } else { " " } } -Title {$_.Name } -Description { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } ConvertFrom-Markdown -Markdown $_.Topic -ScriptAsPowerShell -ShowData:$ShowDataInTopic } -DatePublished { $_.LastWriteTime } -Link { if ($customAnyHandler) { "?About=" + $_.Name } else { $_.Name + "/" } } | Out-RssFeed -Title "$($module.Name) | Topics" -Description "$($module.Description) " -Link "/" $response.ContentType = "text/xml" $strWrite = New-Object IO.StringWriter ([xml]($feed)).Save($strWrite) $resultToOutput = "$strWrite" -replace "encoding=`"utf-16`"", "encoding=`"utf-8`"" $response.Write("$resultToOutput") } } $walkThruRssHandler = { $feed = $walkThrus.Keys | Sort-Object { $walkthrus[$_] | Select-Object -ExpandProperty LastWriteTime -Unique } | New-RssItem -Title { $_ } -Author { if ($module.Author) { $module.Author } else { " " } } -Description { Write-WalkthruHTML -WalkThru ($walkThrus[$_]) } -DatePublished { $walkThrus[$_] | Select-Object -ExpandProperty LastWriteTime -Unique } -Link { if ($customAnyHandler) { "?Walkthru=" + $_ } else { $_ + "/" } } | Out-RssFeed -Title "$($module.Name) | Topics" -Description "$($module.Description) " -Link "/" if ($feed) { $response.ContentType = "text/xml" $strWrite = New-Object IO.StringWriter ([xml]($feed)).Save($strWrite) $resultToOutput = "$strWrite" -replace "encoding=`"utf-16`"", "encoding=`"utf-8`"" $response.Write("$resultToOutput") } } #region About Topic Handler $aboutHandler = { $theTopic = $aboutTopics | Where-Object { $_.SystemName -eq $request['about'].Trim() -or $_.Name -eq $request['About'].Trim() } $topicMatch = if ($theTopic) { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } ConvertFrom-Markdown -Markdown $theTopic.Topic -ScriptAsPowerShell -ShowData:$ShowDataInTopic } else { '<span style=''color:red''>Topic not found</span>' } $page =(New-Region -LayerID "About$($module.Name)Header" -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -Content " <h1 itemprop='name' class='ui-widget-header'><a href='.'>$($module.Name)</a> | $($Request['about'].Replace('_', ' '))</h1> "),(New-Region -Container "About$($module.Name)" -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -AsAccordian -HorizontalRuleUnderTitle -DefaultToFirst -Layer @{ $request['about'].Replace("_", " ") = " <div itemprop='ArticleText'> $topicMatch </div> " }) | New-WebPage -UseJQueryUI -Css $cssStyle -Title "$($module.Name) | About $($Request['about'])" -AnalyticsID "$analyticsId" $response.contentType = 'text/html' $response.Write("$page") } #endregion About Topic Handler #region Show Groups #endregion Show Groups #region Walkthru (Demo) Handler $walkThruHandler = { $pipeworksManifestPath = Join-Path (Split-Path $module.Path) "$($module.Name).Pipeworks.psd1" $pipeworksManifest = if (Test-Path $pipeworksManifestPath) { try { & ([ScriptBlock]::Create( "data -SupportedCommand Add-Member, New-WebPage, New-Region, Write-CSS, Write-Ajax, Out-Html, Write-Link { $( [ScriptBlock]::Create([IO.File]::ReadAllText($pipeworksManifestPath)) )}")) } catch { Write-Error "Could not read pipeworks manifest: ($_ | Out-String)" } } else { $null } $topicMatch = if ($walkthrus.($request['walkthru'].Trim())) { # Use splatting to tack on any extra parameters $params = @{ Walkthru = $walkthrus.($request['walkthru'].Trim()) WalkThruName = $request['walkthru'].Trim() StepByStep = $true } if ($pipeworksManifest.TrustedWalkthrus -contains $request['Walkthru'].Trim()) { $params['RunDemo'] = $true } if ($pipeworksManifest.WebWalkthrus -contains $request['Walkthru'].Trim()) { $params['OutputAsHtml'] = $true } Write-WalkthruHTML @params } else { '<span style=''color:red''>Topic not found</span>' } $page = (New-Region -LayerID "About$($module.Name)Header" -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -Content " <h1 itemprop='name' class='ui-widget-header'><a href='.'>$($module.Name)</a> | $($Request['walkthru'].Replace('_', ' '))</h1> "), (New-Region -LayerId WalkthruContainer -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString } -Content $topicMatch ) | New-WebPage -UseJQueryUI -Css $cssStyle -Title "$($module.Name) | Walkthrus | $($Request['walkthru'].Replace('_', ' '))" -AnalyticsID '$analyticsId' $response.contentType = 'text/html' $response.Write("$page") } #endregion Walkthru (Demo) Handler #region Help Handler $helpHandler = { $RequestedCommand = $Request["GetHelp"] $webCmds = @() $downloadableCmds = @() $cmdOutputDirs = @() $command = $module.ExportedCommands[$RequestedCommand] if (-not $command) { throw "$requestedCommand not found in module $module" } $extraParams = if ($pipeworksManifest -and $pipeworksManifest.WebCommand.($Command.Name)) { @{} + $pipeworksManifest.WebCommand.($Command.Name) } else { @{} } $extraParams.ShowHelp=$true $linkUrl = "$FinalUrl".Substring(0, "$FinalUrl".LastIndexOf("/")) $titleArea = if ($PipeworksManifest -and $pipeworksManifest.Logo) { "<a href='$linkUrl' class='brand'><img src='$($pipeworksManifest.Logo)' alt='$($module)' style='border:0' /></a>" } else { "<a href='$linkUrl' class='brand'>$($Module.Name)</a>" } if (-not $script:SocialArea) { $script:SocialArea = " $( if (-not $antiSocial) { if ($pipeworksManifest -and ($pipeworksManifest.Facebook.AppId -or $pipeworksManifest.FacebookLike)) { Write-Link "facebook:like" } if ($pipeworksManifest -and ($pipeworksManifest.GoogleSiteVerification -or $pipeworksManifest.AddPlusOne)) { Write-Link "google:plusone" } if ($pipeworksManifest -and $pipeworksManifest.ShowTweet) { Write-Link "twitter:tweet" } elseif ($pipeworksManifest -and ($pipeworksManifest.TwitterId)) { Write-Link "twitter:tweet" Write-Link "twitter:follow@$($pipeworksManifest.TwitterId.TrimStart('@'))" } } ) " } $socialArea = $script:SocialArea $result = Invoke-Webcommand -Command $command @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" -ServiceUrl $finalUrl 2>&1 if ($result) { if ($Request.params["AsRss"] -or $Request.params["AsCsv"] -or $Request.params["AsXml"] -or $Request.Params["bare"]) { $response.Write($result) } else { $outputPage = "<div style='float:left'>$($titleArea + $descriptionArea)</div>" + "<div style='float:right'>$socialArea</div>" + +($spacingDiv * 4) +$result | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString }| New-WebPage -Title "$($module.Name) | $command" -UseJQueryUI $response.Write($outputPage) } } } #endregion Help Handler #region Command Handler $commandHandler = { $RequestedCommand = $Request["Command"] . $getCommandExtraInfo $RequestedCommand $result = try { Invoke-Webcommand -Command $command @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" -ServiceUrl $finalUrl 2>&1 } catch { $_ } $linkUrl = "$FinalUrl".Substring(0, "$FinalUrl".LastIndexOf("/")) $titleArea = if ($PipeworksManifest -and $pipeworksManifest.Logo) { "<a href='$linkUrl'><img src='$($pipeworksManifest.Logo)' alt='$($module)' style='border:0' /></a>" } else { "<a href='$linkUrl'>" + $Module.Name + "</a>" } $commandDescription = "" $commandHelp = Get-Help $command -ErrorAction SilentlyContinue | Select-Object -First 1 if ($commandHelp.Description) { $commandDescription = $commandHelp.Description[0].text $commandDescription = $commandDescription -replace "`n", ([Environment]::NewLine) } $descriptionArea = " $(ConvertFrom-Markdown -Markdown "$commandDescription ") " if ($result) { if ($Request.params["AsRss"] -or $Request.params["AsCsv"] -or $Request.params["AsXml"] -or $Request.Params["bare"] -or $extraParams.ContentType -or $extraParams.PlainOutput) { if (-not ($extraParams.ContentType) -and $result -like "*<*>*" -and $result -like '*`$(*)*') { # If it's not HTML or XML, but contains tags, then render it in a page with JQueryUI $outputPage = $socialArea + $spacingDiv + $descriptionArea + $spacingDiv + $result | New-WebPage -Title "$($module.Name) | $command" $response.Write($outputPage) } elseif ($extraParams.ContentType -eq 'text/html' -and $result -like "*$($command)_Input*" -and $result -notlike "*<html>*") { $outputPage = $socialArea + $spacingDiv + $descriptionArea + $spacingDiv + $result | New-WebPage -Title "$($module.Name) | $command" $response.Write($outputPage) } else { $response.Write($result) } } else { if (($result -is [Collections.IEnumerable]) -or ($result -isnot [string])) { $Result = $result | Out-HTML } if ($request["inline"]) { $response.Write($result) } elseif ($request["Snug"] -or $Request["Widget"]) { $outputPage = "<div style='clear:both;margin-top:1%'> </div>" + $result | New-Region -Style @{ "Margin-Left" = "1%" "Margin-Right" = "1%" }| New-WebPage -Title "$($module.Name) | $command" $response.Write($outputPage) } else { $outputPage = $socialArea + $titleArea + "<div style='clear:both;margin-top:1%'></div>" + $descriptionArea + $spacingDiv + $result | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString }| New-WebPage -Title "$($module.Name) | $command" $response.Write($outputPage) } } } } #endregion Command Handler $validateUserTable = { if (-not ($pipeworksManifest.UserTable.Name -and $pipeworksManifest.UserTable.StorageAccountSetting -and $pipeworksManifest.UserTable.StorageKeySetting)) { throw 'The Pipeworks manifest must include these settings in order to manage users: UserTable.Name, UserTable.EmailAddress, UserTable.ExchangeServer, UserTable.ExchangePasswordSetting UserTable.StorageAccountSetting, and UserTable.StorageKeySetting' return } } #region Join Handler $joinHandler = $validateUserTable.ToString() + { $DisplayForm = $false $FormErrors = "" if (-not $request["Join-$($module.Name)_EmailAddress"]) { #$missingFields $displayForm = $true } $newUserData =@{} $missingFields = @() $paramBlock = @() if ($session['ProfileEditMode'] -eq $true) { $editMode = $true } $defaultValue = if ($editMode -and $session['User'].UserEmail) { "|Default $($session['User'].UserEmail)" } else { "" } if ($Request['ReferredBy']) { $session['ReferredBy'] = $Request['ReferredBy'] } $paramBlock += " #$defaultValue [Parameter(Mandatory=`$true,Position=0)] [string] `$EmailAddress " if ($pipeworksManifest.UserTable.RequiredInfo) { $Position = 1 foreach ($k in $pipeworksManifest.UserTable.RequiredInfo.Keys) { $newUserData[$k] = $request["Join-$($module.Name)_${k}"] -as $pipeworksManifest.UserTable.RequiredInfo[$k] $defaultValue = if ($session['User'].$k) { "|Default $($session['User'].$k)" } else { "" } $paramBlock += " #$defaultValue [Parameter(Mandatory=`$true,Position=$position)] [$($pipeworksManifest.UserTable.RequiredInfo[$k].Fullname)] `$$k " $Position++ if (-not $newUserData[$k]) { $missingFields += $k } } } if ($pipeworksManifest.UserTable.OptionalInfo) { foreach ($k in $pipeworksManifest.UserTable.OptionalInfo.Keys) { $newUserData[$k] = $request["Join-$($module.Name)_${k}"] -as $pipeworksManifest.UserTable.OptionalInfo[$k] $defaultValue = if ($session['User'].$k) { "|Default $($session['User'].$k)" } else { "" } $paramBlock += " #${defaultValue} [Parameter(Position=$position)] [$($pipeworksManifest.UserTable.OptionalInfo[$k].Fullname)] `$$k " } } if ($pipeworksManifest.UserTable.TermsOfService) { } .([ScriptBlock]::Create( "function Join-$($module.Name) { <# .Synopsis Joins $($module.Name) or edits a profile .Description #> param( $($paramBlock -join ",$([Environment]::NewLine)") ) } ")) $cmdInput = Get-WebInput -CommandMetaData (Get-Command "Join-$($module.Name)" -CommandType Function) if ($cmdInput.Count -gt 0) { $DisplayForm = $false } if ($missingFields) { $email = $request["Join-$($module.Name)_EmailAddress"] $emailFound = [ScriptBlock]::Create("`$_.UserEmail -eq '$email'") $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageKeySetting) $mailAlreadyExists = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -StorageAccount $storageAccount -StorageKey $storageKey -Where $emailFound if (-not $mailAlreadyExists) { # Get required fields $DisplayForm = $true } elseif ($editMode -and $session['User']) { # Get required fields $DisplayForm = $true } else { # Reconfirm $DisplayForm = $false } } $sendMailParams = @{ BodyAsHtml = $true To = $request["Join-$($module.Name)_EmailAddress"] } $sendMailCommand = if ($pipeworksManifest.UserTable.SmtpServer -and $pipeworksManifest.UserTable.FromEmail -and $pipeworksManifest.UserTable.FromUser -and $pipeworksManifest.UserTable.EmailPasswordSetting) { $($ExecutionContext.InvokeCommand.GetCommand("Send-MailMessage", "All")) $un = $pipeworksManifest.UserTable.FromUser $pass = Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.EmailPasswordSetting $pass = ConvertTo-SecureString $pass -AsPlainText -Force $cred = New-Object Management.Automation.PSCredential ".\$un", $pass $sendMailParams += @{ SmtpServer = $pipeworksManifest.UserTable.SmtpServer From = $pipeworksManifest.UserTable.FromEmail Credential = $cred UseSsl = $true } } else { $($ExecutionContext.InvokeCommand.GetCommand("Send-Email", "All")) $sendMailParams += @{ UseWebConfiguration = $true AsJob = $true } } if ($displayForm) { $formErrors = if ($missingFields -and ($cmdInput.Count -ne 0)) { "Missing $missingFields" } else { } $buttonText = if ($mailAlreadyExists -or $session['User']) { "Edit Profile" } else { "Join / Login" } $response.Write(" $FormErrors $(Request-CommandInput -ButtonText $buttonText -Action "${FinalUrl}?join=true" -CommandMetaData (Get-Command "Join-$($module.Name)" -CommandType Function)) ") } else { $session['UserEmail'] = $request["Join-$($module.Name)_EmailAddress"] $session['UserData'] = $newUserData $session['EditMode'] = $editMode $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageKeySetting) $email = $Session['UserEmail'] $editMode = $session['EditMode'] $session['EditMode'] = $null $emailFound = [ScriptBlock]::Create("`$_.UserEmail -eq '$email'") $userProfilePartition = if (-not $pipeworksManifest.UserTable.Partition) { "UserProfiles" } else { $pipeworksManifest.UserTable.Partition } $mailAlreadyExists = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -StorageAccount $storageAccount -StorageKey $storageKey -Where $emailFound | Where-Object { $_.PartitionKey -eq $userProfilePartition } $newUserObject = New-Object PSObject -Property @{ UserEmail = $Session['UserEmail'] UserID = [GUID]::NewGuid() Confirmed = $false Created = Get-Date } $ConfirmCode = [Guid]::NewGuid() $newUserObject.pstypenames.clear() $newUserObject.pstypenames.add("$($module.Name)_UserInfo") $extraPropCommonParameters = @{ InputObject = $newUserObject MemberType = 'NoteProperty' } Add-Member @extraPropCommonParameters -Name ConfirmCode -Value "$confirmCode" if ($session['UserData']) { foreach ($kvp in $session['UserData'].GetEnumerator()) { Add-Member @extraPropCommonParameters -Name $kvp.Key -Value $kvp.Value } } $commonAzureParameters = @{ TableName = $pipeworksManifest.UserTable.Name PartitionKey = $userProfilePartition } if ($mailAlreadyExists) { if ((-not $editMode) -or (-not $session['User'])) { # Creating a brand new item via the email system. Email the confirmation code out. $rootLocation= "$finalUrl".Substring(0, $finalUrl.LAstIndexOf("/")) $introMessage = if ($pipeworksManifest.UserTable.IntroMessage) { $pipeworksManifest.UserTable.IntroMessage + "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Confirm Email Address</a>" } else { "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Re-confirm Email Address to login</a>" } $sendMailParams += @{ Subject= "Please re-confirm your email for $($module.Name)" Body = $introMessage } & $sendMailcommand @sendMailParams "Account already exists. A request to login has been sent to $($mailAlreadyExists.UserEmail)." | New-WebPage -Title "Email address is already registered, sending reconfirmation mail" -RedirectTo $rootLocation -RedirectIn "0:0:5" | Out-HTML -WriteResponse # <# Send-Email -To $newUserObject.UserEmail -UseWebConfiguration - -Body $introMessage -BodyAsHtml -AsJob "Account already exists. A request to login has been sent to $($mailAlreadyExists.UserEmail)." | New-WebPage -Title "Email address is already registered, sending reconfirmation mail" -RedirectTo $rootLocation -RedirectIn "0:0:5" | Out-HTML -WriteResponse #> $mailAlreadyExists | Add-Member NoteProperty ConfirmCode "$confirmCode" -Force -PassThru | Update-AzureTable @commonAzureParameters -RowKey $mailAlreadyExists.RowKey -Value { $_} } else { # Reconfirmation of Changes. If the user is logged in via facebook, then simply make the change. Otherwise, make the changes pending. if (-not $pipeworksManifest.Facebook.AppId) { $introMessage = "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Please confirm changes to your $($module.Name) account</a>" $introMessage += "<br/><br/>" $introMessage += New-Object PSObject -Property $session['UserData'] | Out-HTML $sendMailParams += @{ Subject= "Please confirm changes to your $($module.Name) account" Body = $introMessage } & $sendMailcommand @sendMailParams "An email has been sent to $($mailAlreadyExists.UserEmail) to confirm the changes to your acccount" | New-WebPage -Title "Confirming Changes" -RedirectTo $rootLocation -RedirectIn "0:0:5" | Out-HTML -WriteResponse $mailAlreadyExists | Add-Member NoteProperty ConfirmCode "$confirmCode" -Force -PassThru | Update-AzureTable @commonAzureParameters -RowKey $mailAlreadyExists.RowKey -Value { $_} $changeToMake = @{} + $commonAzureParameters $changeToMake.PartitionKey = "${userProfilePartition}_PendingChanges" # Create a row in the pending change table $newUserObject.psobject.properties.Remove('ConfirmCode') $newUserObject | Set-AzureTable @changeToMake -RowKey {[GUID]::NewGuid() } } else { # Make the profile change $newUserObject | Update-AzureTable @commonAzureParameters -RowKey $mailAlreadyExists.RowKey } } } else { if ($pipeworksManifest.UserTable.BlacklistParition) { $blackList = Search-AzureTable -TableName $pipeworks.UserTable.Name -Filter "PartitionKey eq '$($pipeworksManifest.UserTable.BlacklistParition)'" if ($blacklist) { foreach ($uInfo in $Blacklist) { if ($newUserObject.UserEmail -like "*$uInfo*") { Write-Error "$($newUserObject.UserEmai) is blacklisted from $($module.Name)" return } } } } if ($pipeworksManifest.UserTable.WhitelistPartition) { $whiteList = Search-AzureTable -TableName $pipeworks.UserTable.Name -Filter "PartitionKey eq '$($pipeworksManifest.UserTable.WhitelistParition)'" if ($whiteList) { $inWhiteList = $false foreach ($uInfo in $whiteList) { if ($newUserObject.UserEmail -like "*$uInfo*") { $inWhiteList = $true break } } if (-not $inWhiteList) { Write-Error "$($newUserObject.UserEmail) is not on the whitelist for $($module.Name)" } } } if ($pipeworksManifest.UserTable.InitialBalance) { $newUserObject | Add-Member NoteProperty Balance (0- ([Double]$pipeworksManifest.UserTable.InitialBalance)) } if ($session['RefferedBy']) { $newUserObject | Add-Member NoteProperty RefferedBy $session['RefferedBy'] -PassThru | Add-Member NoteProperty RefferalCreditApplied $false } $newUserObject | Set-AzureTable @commonAzureParameters -RowKey $newUserObject.UserId $introMessage = if ($pipeworksManifest.UserTable.IntroMessage) { $pipeworksManifest.UserTable.IntroMessage + "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Confirm Email Address</a>" } else { "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Confirm Email Address</a>" } $sendMailParams += @{ Subject= "Please confirm your email for $($module.Name)" Body = $introMessage } & $sendMailcommand @sendMailParams if ($passThru) { $newUserObject } $almostWelcomeScreen = if ($pipeworksManifest.UserTable.ConfirmationMailSent) { $pipeworksManifest.UserTable.ConfirmationMailSent } else { "A confirmation mail has been sent to $($newUserObject.UserEmail)" } $html = New-Region -Content $almostWelcomeScreen -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' } | New-WebPage -Title "Welcome to $($module.Name) | Confirmation Mail Sent" $response.Write($html) } } } if ($useLoginHandlers) { $joinHandler = [ScriptBlock]::Create($joinHandler) } else { $joinHandler = {} } #endregion $AddUserStat = $validateUserTable.ToString() + { if (-not $session["User"]) { throw "Must be logged in" } } $ShowApiKeyHandler = $validateUserTable.ToString() + { if ($request.Cookies["$($module.Name)_ConfirmationCookie"]) { $response.Write($request.Cookies["$($module.Name)_ConfirmationCookie"]["Key"]) } } $logoutUserHandler = $validateUserTable.ToString() + { $secondaryApiKey = $session["$($module.Name)_ApiKey"] $confirmCookie = New-Object Web.HttpCookie "$($module.Name)_ConfirmationCookie" $confirmCookie["Key"] = "$secondaryApiKey" $confirmCookie["CookiedIssuedOn"] = (Get-Date).ToString("r") $confirmCookie.Expires = (Get-Date).AddDays(-365) $response.Cookies.Add($confirmCookie) $session['User'] = $null $html = New-WebPage -Title "Logging Out" -RedirectTo "$finalUrl" $response.Write($html) } $loginUserHandler = $validateUserTable.ToString() + { $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageKeySetting) $confirmCookie= $Request.Cookies["$($module.Name)_ConfirmationCookie"] if ($confirmCookie) { $matchApiInfo = [ScriptBLock]::Create("`$_.SecondaryApiKey -eq '$($confirmCookie.Values['Key'])'") $userFound = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -StorageAccount $storageAccount -StorageKey $storageKey -Where $matchApiInfo if (-not $userFound) { $secondaryApiKey = $session["$($module.Name)_ApiKey"] $confirmCookie = New-Object Web.HttpCookie "$($module.Name)_ConfirmationCookie" $confirmCookie["Key"] = "$secondaryApiKey" $confirmCookie["CookiedIssuedOn"] = (Get-Date).ToString("r") $confirmCookie.Expires = (Get-Date).AddDays(-365) $response.Cookies.Add($confirmCookie) $response.Flush() $response.Write("User $($confirmCookie | Out-String) Not Found, ConfirmationCookie Set to Expire") return } $userIsConfirmed = $userFound | Where-Object { $_.Confirmed -ilike "*$true*" } $userIsConfirmedOnThisMachine = $userIsConfirmed | Where-Object { $_.ConfirmedOn -ilike "*$($Request['REMOTE_ADDR'] + $request['REMOTE_HOST'])*" } $sendMailParams = @{ BodyAsHtml = $true To = $newUserObject.UserEmail } $sendMailCommand = if ($pipeworksManifest.UserTable.SmtpServer -and $pipeworksManifest.UserTable.FromEmail -and $pipeworksManifest.UserTable.FromUser -and $pipeworksManifest.UserTable.EmailPasswordSetting) { $($ExecutionContext.InvokeCommand.GetCommand("Send-MailMessage", "All")) $un = $pipeworksManifest.UserTable.FromUser $pass = Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.EmailPasswordSetting $pass = ConvertTo-SecureString $pass -AsPlainText -Force $cred = New-Object Management.Automation.PSCredential ".\$un", $pass $sendMailParams += @{ SmtpServer = $pipeworksManifest.UserTable.SmtpServer From = $pipeworksManifest.UserTable.FromEmail Credential = $cred UseSsl = $true } } else { $($ExecutionContext.InvokeCommand.GetCommand("Send-Email", "All")) $sendMailParams += @{ UseWebConfiguration = $true AsJob = $true } } if (-not $userIsConfirmedOnThisMachine) { $confirmCode = [guid]::NewGuid() Add-Member -MemberType NoteProperty -InputObject $userIsConfirmed -Name ConfirmCode -Force -Value "$confirmCode" $introMessage = if ($pipeworksManifest.UserTable.IntroMessage) { $pipeworksManifest.UserTable.IntroMessage + "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Confirm Email Address</a>" } else { "<br/> <a href='${finalUrl}?confirmUser=$confirmCode'>Confirm Email Address</a>" } $sendMailParams += @{ Subject= "Welcome to $($module.Name)" Body = $introMessage } & $sendMailcommand @sendMailParams # Send-Email -To $userIsConfirmed.UserEmail -UseWebConfiguration -Subject -Body $introMessage -BodyAsHtml -AsJob $partitionKey = $userIsConfirmed.PartitionKey $rowKey = $userIsConfirmed.RowKey $tableName = $userIsConfirmed.TableName $userIsConfirmed.psobject.properties.Remove('PartitionKey') $userIsConfirmed.psobject.properties.Remove('RowKey') $userIsConfirmed.psobject.properties.Remove('TableName') $userIsConfirmed | Update-AzureTable -TableName $tableName -RowKey $rowKey -PartitionKey $partitionKey -Value { $_} $message = "User Not confirmed on this machine/ IPAddress. A confirmation mail has been sent to $($userFound.UserEmail)" $html = New-Region -Content $message -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' } | New-WebPage -Title "$($module.Name)| Login Error: Unrecognized Machine" $response.Write("$html") return } else { $session['User'] = $userIsConfirmedOnThisMachine $session['UserId'] = $userIsConfirmedOnThisMachine.UserId $welcomeBackMessage = "Welcome back " + $( if ($userIsConfirmedOnThisMachine.Name) { $userIsConfirmedOnThisMachine.Name } else { $userIsConfirmedOnThisMachine.UserEmail } ) $secondaryApiKey = "$($confirmCookie.Values['Key'])" $backToUrl = if ($session['BackToUrl']) { $session['BackToUrl'] $session['BackToUrl'] = $null } else { $finalUrl.ToString().Substring(0,$finalUrl.ToString().LastIndexOf("/")) } $html = New-Region -Content $welcomeBackMessage -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' } | New-WebPage -Title "Welcome to $($module.Name)" -RedirectTo $backToUrl -RedirectIn "0:0:0.125" $response.Write("$html") $partitionKey = $userIsConfirmedOnThisMachine.PartitionKey $rowKey = $userIsConfirmedOnThisMachine.RowKey $tableName = $userIsConfirmedOnThisMachine.TableName $userIsConfirmedOnThisMachine.psobject.properties.Remove('PartitionKey') $userIsConfirmedOnThisMachine.psobject.properties.Remove('RowKey') $userIsConfirmedOnThisMachine.psobject.properties.Remove('TableName') $userIsConfirmedOnThisMachine | Add-Member -MemberType NoteProperty -Name LastLogon -Force -Value (Get-Date) $userIsConfirmedOnThisMachine | Add-Member -MemberType NoteProperty -Name LastLogonFrom -Force -Value "$($Request['REMOTE_ADDR'] + $request['REMOTE_HOST'])" $userIsConfirmedOnThisMachine | Update-AzureTable -TableName $tableName -RowKey $rowKey -PartitionKey $partitionKey -Value { $_} $session['User'] = $userIsConfirmedOnThisMachine } } else { $html = New-WebPage -Title "User Information Not Found - Redirecting to Signup Page" -RedirectTo "${finalUrl}?join=true" $response.Write($html) return } } $confirmUserHandler = $validateUserTable.ToString() + { $confirmationCode = [Web.HttpUtility]::UrlDecode($request['confirmUser']).TrimEnd(" ").TrimEnd("#").TrimEnd(">").TrimEnd("<") $session['ProfileEditMode'] = $false $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageKeySetting) $confirmCodeFilter = [ScriptBLock]::Create("`$_.ConfirmCode -eq '$confirmationCode'") $confirmationCodeFound = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -StorageAccount $storageAccount -StorageKey $storageKey -Where $confirmCodeFilter if (-not $confirmationCodeFound) { Write-Error "Confirmation Code Not Found" return } $confirmedOn = ($confirmationCodeFound.ConfirmedOn + "," + ($Request['REMOTE_ADDR'] + $request['REMOTE_HOST'])) -split "," -ne "" | Select-Object -Unique $confirmSalts = @($confirmationCodeFound.ConfirmSalt -split "\|") $confirmationCodeFound | Add-Member NoteProperty Confirmed $true -Force $confirmationCodeFound | Add-Member NoteProperty ConfirmedOn ($confirmedOn -join ',') -Force # When we confirm the item, we set two cookies. One keeps the Secondary API key, and the other a confirmation salt. Both are HTTP only $ThisConfirmationSalt = [GUID]::NewGuid() $confirmSalts += $ThisConfirmationSalt $confirmationCodeFound | Add-Member NoteProperty ConfirmedOn ($confirmedOn -join ',') -Force <# $confirmationCodeFound | Add-Member NoteProperty ConfirmSalt ($confirmSalts -join '|') -Force #> if (-not $confirmationCodeFound.PrimaryApiKey) { $primaryApiKey =[guid]::NewGuid() $secondaryApiKey = [guid]::NewGuid() $confirmationCodeFound | Add-Member NoteProperty PrimaryApiKey "$primaryApiKey" -PassThru -ErrorAction SilentlyContinue | Add-Member NoteProperty SecondaryApiKey "$secondaryApiKey" -ErrorAction SilentlyContinue } else { $primaryApiKey = $confirmationCodeFound.PrimaryApiKey $secondaryApiKey = $confirmationCodeFound.SecondaryApiKey $sessionApiKey = [Convert]::ToBase64String(([Guid]$secondaryApiKey).ToByteArray()) $session["$($module.Name)_ApiKey"] = $sessionApiKey } $confirmCookie = New-Object Web.HttpCookie "$($module.Name)_ConfirmationCookie" $confirmCookie["Key"] = "$secondaryApiKey" $confirmCookie["CookiedIssuedOn"] = (Get-Date).ToString("r") $confirmCookie["ConfirmationSalt"] = $ThisConfirmationSalt $confirmCookie["Email"] = $confirmationCodeFound.UserEmail $confirmCookie.Expires = (Get-Date).AddDays(365) $response.Cookies.Add($confirmCookie) $partitionKey = $confirmationCodeFound.PartitionKey $rowKey = $confirmationCodeFound.RowKey $tableName = $confirmationCodeFound.TableName $confirmCount =$confirmationCodeFound.ConfirmCount -as [int] $confirmCount++ $confirmationCodeFound | Add-Member NoteProperty ConfirmCount $ConfirmCount -Force $confirmationCodeFound.psobject.properties.Remove('PartitionKey') $confirmationCodeFound.psobject.properties.Remove('RowKey') $confirmationCodeFound.psobject.properties.Remove('TableName') $confirmationCodeFound.psobject.properties.Remove('ConfirmCode') # At this point they are actually confirmed $confirmationCodeFound | Update-AzureTable -TableName $pipeworksManifest.UserTable.Name -RowKey $rowKey -PartitionKey $partitionKey -Value { $_} if ($confirmationCodeFound.ConfirmCount -eq 1 ) { $ConfirmMessage = @" $($pipeworksManifest.UserTable.WelcomeEmailMessage) <BR/> Thanks for confirming,<br/> <br/> Your API key is: $secondaryApiKey <br/> <br/> Whenever you need to use a software service in $($module.Name), use this API key. (It's also being emailed to you) "@ $html = New-Region -Content $confirmMessage -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' } | New-WebPage -Title "Welcome to $($module.Name)" -RedirectTo "${finalUrl}?login=true" -RedirectIn "0:0:5" $session['User'] = Get-AzureTable -TableName $pipeworksManifest.UserTable.Name -RowKey $rowKey -PartitionKey $partitionKey $response.Write("$html") $sendMailParams = @{ BodyAsHtml = $true To = $newUserObject.UserEmail } $sendMailCommand = if ($pipeworksManifest.UserTable.SmtpServer -and $pipeworksManifest.UserTable.FromEmail -and $pipeworksManifest.UserTable.FromUser -and $pipeworksManifest.UserTable.EmailPasswordSetting) { $($ExecutionContext.InvokeCommand.GetCommand("Send-MailMessage", "All")) $un = $pipeworksManifest.UserTable.FromUser $pass = Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.EmailPasswordSetting $pass = ConvertTo-SecureString $pass -AsPlainText -Force $cred = New-Object Management.Automation.PSCredential ".\$un", $pass $sendMailParams += @{ SmtpServer = $pipeworksManifest.UserTable.SmtpServer From = $pipeworksManifest.UserTable.FromEmail Credential = $cred UseSsl = $true } } else { $($ExecutionContext.InvokeCommand.GetCommand("Send-Email", "All")) $sendMailParams += @{ UseWebConfiguration = $true AsJob = $true } } $sendMailParams += @{ Subject= "Welcome to $($module.Name)" Body = @" $($pipeworksManifest.UserTable.WelcomeEmailMessage) <BR/> Thanks for confirming,<br/> <br/> Your API key is: $secondaryApiKey <br/> <br/> "@ } & $sendMailcommand @sendMailParams # Send-Email -UseWebConfiguration -AsJob -To $confirmationCodeFound.UserEmail -BodyAsHtml } else { # Check to see if this is confirming an update, and make the changes $emailFilter = [ScriptBlock]::Create("`$_.UserEmail -eq '$($confirmationCodeFound.UserEmail)'") $emailEditsByTime = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -Where $emailFilter | Sort-Object { [DateTime]$_.Timestamp } $userProfilePartition = if (-not $pipeworksManifest.UserTable.Partition) { "UserProfiles" } else { $pipeworksManifest.UserTable.Partition } $original = $emailEditsByTime| Where-Object { $_.PartitionKey -eq $userProfilePartition } | Select-Object -First 1 $update = $emailEditsByTime| Where-Object { $_.PartitionKey -ne $userProfilePartition } | Select-Object -Last 1 if ($original -and $update) { $changeProperties = @($pipeworksManifest.UserTable.RequiredInfo.Keys) + @($pipeworksManifest.UserTable.OptionalInfo.Keys) $toChange = $update | Select-Object -First 1 | Select-Object $changeProperties foreach ($prop in $toChange.psobject.properties) { $original | Add-Member NoteProperty $prop.Name $prop.Value -Force } $original | Update-AzureTable -TableName $pipeworksManifest.UserTable.name -PartitionKey $userProfilePartition -RowKey $original.UserId -Value { $_} $userInfo = Get-AzureTable -TableName $pipeworksManifest.UserTable.name -PartitionKey $userProfilePartition -RowKey $original.UserId $session['User'] = $userInfo $update | Remove-AzureTable -Confirm:$false $ConfirmMessage = @" <BR/> Thanks for confirming. The following changes have been made to your account:<br/> $($toChange | Out-HTML) "@ } else { $ConfirmMessage = @" $($pipeworksManifest.UserTable.WelcomeBackMessage) <BR/> Thanks for re-confirming, and welcome back<br/> "@ } $html = New-Region -Content $confirmMessage -AsWidget -Style @{ 'margin-left' = $MarginPercentLeftString 'margin-right' = $MarginPercentRightString 'margin-top' = '10px' 'border' = '0px' } | New-WebPage -Title "Welcome back to $($module.Name)" -RedirectTo "${finalUrl}?login=true" $response.Write("$html") } } $TextHandler = { # Handle text input for all commands in WebCommand } $MeHandler = { $confirmPersonHtml = . Confirm-Person -WebsiteUrl $finalUrl if ($session -and $session["User"]) { $profilePage = $session["User"] | Out-HTML | New-WebPage $response.Write($profilePage) } else { throw "Not Logged In" } } if (-not $useLoginHandlers) { $MeHandler = {} } $settleHandler = $validateUserTable.ToString() + { if (-not ($session -and $session["User"])) { throw "Not Logged in" } New-WebPage -RedirectTo "?Purchase=true&ItemName=Settle Account Balance&ItemPrice=$($session["User"].Balance)" | Out-html -writeresponse } $buywithCodeHandler = $validateUserTable.ToString() + { if (-not $pipeworksManifest.PaymentProcessing.BuyCodeHandler) { return } if (-not $pipeworksManifest.PaymentProcessing.BuyCodePartition) { return } if (-not ($request -and $request["PotentialBuyCode"])) { return } $storageAccount = Get-SecureSetting $pipeworksManifest.UserTable.StorageAccountSetting -ValueOnly $storageKey = Get-SecureSetting $pipeworksManifest.UserTable.StorageKeySetting -ValueOnly $buyCodeFound = Get-AzureTable -StorageAccount $storageAccount -StorageKey $storageAccount -PartitionKey $pipeworksManifest.PaymentProcessing.BuyCodePartition -RowKey $($request["PotentialBuyCode"]) if ($buyCodeFound) { if (($buyCode.NumberOfUses -as [int]) -ge 0) { } else { "Buy code not found" | New-WebPage -Title "Buy code not found" | Out-html -writeresponse } } else { "Buy code not found" | New-WebPage -Title "Buy code not found" | Out-html -writeresponse } <# } else { } #> } $addCartHandler = { if (-not ($request -and $request["ItemId"] -and $request["ItemName"] -and $Request["ItemPrice"])) { throw "Must provide an ItemID and ItemName and ItemPrice" } $cartCookie = $request.Cookies["$($module.Name)_CartCookie"] if (-not $cartCookie) { $CartCookie = New-Object Web.HttpCookie "$($module.Name)_CartCookie" } $CartCookie["Item_" + $request["ItemID"]]= $request["ItemName"] + "|" + $request["ItemPrice"] $CartCookie["LastUpdatedOn"] = (Get-Date).ToString("r") $CartCookie.Expires = (Get-Date).AddMinutes(60) $response.Cookies.Add($CartCookie ) $response.Write("<p style='display:none'>") $response.Flush() return } $showCartHandler = { $cartCookie = $request.Cookies["$($module.Name)_CartCookie"] if (-not $cartCookie) { $CartCookie = New-Object Web.HttpCookie "$($module.Name)_CartCookie" } $cartCookie.Values.GetEnumerator() | Where-Object { $_ -like "Item_*" } | Foreach-Object -Begin { $items = @() } { $itemId = $_.Replace("Item_", "") $itemName, $itemPrice = $cartCookie.Values[$_] -split "\|" if (-not ($itemPrice -as [Double])) { if ($itemPrice.Substring(1) -as [Double]) { $itemPrice = $itemPrice.Substring(1) } } $items+= New-Object PSObject | Add-Member NoteProperty Name $itemName -passthru | Add-Member NoteProperty Price ($itemPrice -as [Double]) -passthru } -End { $subtotal = $items | Measure-Object -Sum Price | Select-Object -ExpandProperty Sum ($items | Out-HTML ) + "<HR/>" + ("<div style='float:right;text-align:right'><b>Subtotal:</b><br/><br/><span style='margin:5px'>$Subtotal</span></div><div style='clear:both'></div>") }| Out-HTML -WriteResponse if ($session -and $session["User"]) { } else { function Request-ContactInfo { <# .Synopsis .Description Please let us know how to get in touch with you in case there's a problem with your order .Example #> param( # Your Name [string] $Name, # Your email [string] $Email, # Your Phone number [string] $PhoneNumber ) } Request-CommandInput -CommandMetaData (Get-Command Request-Contactinfo) -ButtonText "Checkout" -Action "${finalUrl}?Checkout=true" | Out-HTML -WriteResponse } return } $checkoutCartHandler = { if ($pipeworksManifest.Checkout.To -and $pipeworksManifest.Checkout.SmtpServer -and $pipeworksManifest.Checkout.SmtpUserSetting -and $pipeworksManifest.Checkout.SmtpPasswordSetting) { # Email based cart, send the order along $emailContent = $cartCookie.Values.GetEnumerator() | Where-Object { $_ -like "Item_*" } | Foreach-Object -Begin { $items = @() } { $itemId = $_.Replace("Item_", "") $itemName, $itemPrice = $cartCookie.Values[$_] -split "\|" if (-not ($itemPrice -as [Double])) { if ($itemPrice.Substring(1) -as [Double]) { $itemPrice = $itemPrice.Substring(1) } } $items+= New-Object PSObject | Add-Member NoteProperty Name $itemName -passthru | Add-Member NoteProperty Price ($itemPrice -as [Double]) -passthru } -End { $subtotal = $items | Measure-Object -Sum Price | Select-Object -ExpandProperty Sum ($items | Out-HTML ) + "<HR/>" + ("<div style='float:right;text-align:right'><b>Subtotal:</b><br/><br/><span style='margin:5px'>$Subtotal</span></div><div style='clear:both'></div>") } $emailAddress = Get-SecureSetting -Name $pipeworksManifest.Checkout.SmtpUserSetting $emailPassword = Get-SecureSetting -Name $pipeworksManifest.Checkout.SmtpPasswordSetting $emailCred = New-Object Management.Automation.PSCredential ".\$emailAddress", (ConvertTo-SecureString -AsPlainText -Force $emailPassword) $to = $pipeworksManifest.Checkout.To -split "\|" Send-MailMessage -SmtpServer $pipeworksManifest.Checkout.SmtpServer -From $emailAddress -UseSsl -BodyAsHtml -Body $emailContent -Subject "Order From $from" -To $to -Credential $emailCred } } $AddPurchaseHandler = $validateUserTable.ToString() + { if ($request["Rent"]) { $isRental = $true $billingFrequency = $request["BillingFrequency"] } else { $isRental = $false $billingFrequency = "" } if (-not ($session -and $session["User"])) { throw "Not Logged in" } if (-not ($Request -and $request["ItemName"])) { throw "Must Provide an ItemName" } if (-not ($Request -and $request["ItemPrice"])) { throw "Must Provide an ItemPrice" } $currency = "USD" if ($request -and $request["Currency"]) { $currency = $reqeust["Currency"] } if (-not ($Request -and $request["ItemPrice"])) { throw "Must Provide an ItemPrice" } $PostPaymentParameter,$postPaymentCommand = $null if ($session["PostPaymentCommand"]) { $postPaymentCommand= $session["PostPaymentCommand"] if ($session["PostPaymentParameter"]) { try { $PostPaymentParameter= $session["PostPaymentParameter"] } catch { } } } $userPart = if ($pipeworksManifest.UserTable.Partition) { $pipeworksManifest.UserTable.Partition } else { "Users" } $purchaseHistory = $userPart + "_Purchases" $purchaseId = [GUID]::NewGuid() $purchase = New-Object PSObject $purchase.pstypenames.clear() $purchase.pstypenames.add('http://shouldbeonschema.org/ReceiptItem') $purchase = $purchase | Add-Member NoteProperty PurchaseId $purchaseId -PassThru | Add-Member NoteProperty ItemName $request["ItemName"] -PassThru | Add-Member NoteProperty ItemPrice $request["ItemPrice"] -PassThru | Add-Member NoteProperty Currency $request["Currency"] -PassThru | Add-Member NoteProperty OrderTime $request["OrderTime"] -PassThru | Add-Member NoteProperty UserID $session["User"].UserID -PassThru if ($postPaymentCommand) { $purchase = $purchase | Add-Member NoteProperty PostPaymentCommand $postPaymentCommand -PassThru } if ($PostPaymentParameter) { $purchase = $purchase | Add-Member NoteProperty PostPaymentParameter $PostPaymentParameter -PassThru } $azureStorageAccount = Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageAccountSetting $azureStorageKey= Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageKeySetting $purchase | Set-AzureTable -TableName $pipeworksManifest.UserTable.Name -PartitionKey $purchaseHistory -RowKey $purchaseId -StorageAccount $azureStorageAccount -StorageKey $azureStorageKey $payLinks = "" $payLinks += if ($pipeworksManifest.PaymentProcessing.AmazonPaymentsAccountId -and $pipeworksManifest.PaymentProcessing.AmazonAccessKey) { Write-Link -ItemName $request["ItemName"] -Currency $currency -ItemPrice $request["ItemPrice"] -AmazonPaymentsAccountId $pipeworksManifest.PaymentProcessing.AmazonPaymentsAccountId -AmazonAccessKey $pipeworksManifest.PaymentProcessing.AmazonAccessKey } $payLinks += if ($pipeworksManifest.PaymentProcessing.PaypalEmail) { Write-Link -ItemName $request["ItemName"] -Currency $currency -ItemPrice $request["ItemPrice"] -PaypalEmail $pipeworksManifest.PaymentProcessing.PaypalEmail -PaypalIPN "${FinalUrl}?-PaypalIPN" -PaypalCustom $purchaseId -Subscribe:$isRental } $paypage = $payLinks | New-WebPage -Title "Buy $($Request["ItemName"]) for $($Request["ItemPrice"])" $paypage| Out-html -writeresponse if ($PipeworksManifest.Mail.SmtpServer -and $pipeworksManifest.Mail.SmtpUserSetting -and $pipeworksManifest.Mail.SmtpPasswordSetting -and $pipeworksManifest.Mail.From) { $smtpServer = $pipeworksManifest.Mail.SmtpServer $smtpUser = Get-WebConfigurationSetting -Setting $pipeworksManifest.Mail.SmtpUserSetting $smtpPassword = Get-WebConfigurationSetting -Setting $pipeworksManifest.Mail.SmtpPasswordSetting $smtpCred = New-Object Management.Automation.PSCredential ".\$smtpUser", (ConvertTo-SecureString -String $smtpPassword -AsPlainText -Force) Send-MailMessage -UseSsl -SmtpServer $smtpServer -Subject $Request["ItemName"] -Body $payPage -BodyAsHtml -Credential $smtpCred } } $payPalIpnHandler = $validateUserTable.ToString() + { $error.Clear() $userPart = if ($pipeworksManifest.UserTable.Partition) { $pipeworksManifest.UserTable.Partition } else { "Users" } $purchaseHistory = $userPart + "_Purchases" $req = [Net.HttpWebRequest]::Create("https://www.paypal.com/cgi-bin/webscr") # //Set values for the request back $req.Method = "POST"; $req.ContentType = "application/x-www-form-urlencoded" $strRequest = $request.Form.ToString() + "&cmd=_notify-validate"; $req.ContentLength = $strRequest.Length; $parsed = [Web.HttpUtility]::ParseQueryString($strRequest) $streamOut = New-Object IO.StreamWriter $req.GetRequestStream() $streamOut.Write($strRequest); $streamOut.Close(); $streamIn = New-Object IO.StreamReader($req.GetResponse().GetResponseStream()); $strResponse = $streamIn.ReadToEnd(); $streamIn.Close(); $azureStorageAccount = Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageAccountSetting $azureStorageKey= Get-WebConfigurationSetting -Setting $pipeworksManifest.UserTable.StorageKeySetting $custom = $Request["Custom"] $ipnResponse = $strResponse $ipn = New-Object PSObject -Property @{ Custom = "$custom" Request = $strRequest IPNResponse = $ipnResponse UserPart = $purchaseHistory } $ipn | Set-AzureTable -TableName $pipeworksManifest.UserTable.Name -RowKey { [GUID]::NewGuid() } -PartitionKey "PaypalIPN" -StorageAccount $azureStorageAccount -StorageKey $azureStorageKey if ($ipnResponse -eq "VERIFIED") { <# //check the payment_status is Completed //check that txn_id has not been previously processed //check that receiver_email is your Primary PayPal email //check that payment_amount/payment_currency are correct //process payment #> $filterString = "PartitionKey eq '$purchaseHistory' and RowKey eq '$($ipn.Custom)'" $transactionExists = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -Filter $filterString if ($transactionExists) { # Alter the user balance if ($transactionExists.Processed -like "True*") { # already processed, skip New-Object PSObject -Property @{ Custom = "$custom" SkippingProcessedTransaction=$true } | Set-AzureTable -TableName $pipeworksManifest.UserTable.Name -RowKey { [GUID]::NewGuid() } -PartitionKey "PaypalIPN" return } $result = " " if ($request["Payment_Status"] -ne "Completed") { New-Object PSObject -Property @{ Custom = $custom TransactionIncomplete = $request["Payment_Status"] } | Set-AzureTable -TableName $pipeworksManifest.UserTable.Name -RowKey { [GUID]::NewGuid() } -PartitionKey "PaypalIPN" } else { $userInfo = Search-AzureTable -TableName $pipeworksManifest.UserTable.Name -Filter "PartitionKey eq '$userPart' and RowKey eq '$($transactionExists.UserID)'" $balance = $userInfo.Balance -as [Double] $balance -= $request["payment_gross"] -as [Double] $userInfo | Add-Member NoteProperty Balance $balance -Force -PassThru | Update-AzureTable -TableName $pipeworksManifest.UserTable.Name -Value { $_ } $session["User"] = $userInfo if ($transactionExists.postPaymentCommand) { $postPaymentCommand = Get-Command -Module $module.Name -Name "$($transactionExists.postPaymentCommand)".Trim() $PostPaymentParameter = if ($transactionExists.postPaymentParameter) { invoke-expression "data { $($transactionExists.postPaymentParameter) }" } else { @{} } $extra = if ($pipeworksManifest.WebCommand."$($transactionExists.postPaymentCommand)".Trim()) { $pipeworksManifest.WebCommand."$($transactionExists.postPaymentCommand)".Trim() } else { @{} } if ($extra.RunWithoutInput) { $null = $extra.Remove("RunWithoutInput") } if ($extra.ParameterDefaultValue) { try { $postPaymentParameter += $extra.ParameterDefaultValue } catch { } $null = $extra.Remove("ParameterDefaultValue") } if ($extra.RequireAppKey -or $extra.RequireLogin -or $extra.IfLoggedAs -or $extra.ValidUserPartition -or $extra.Cost -or $extra.CostFactor) { $extra.UserTable = $pipeworksManifest.Usertable.Name $extra.UserPartition = $pipeworksManifest.Usertable.Partition $extra.StorageAccountSetting = $pipeworksManifest.Usertable.StorageAccountSetting $extra.StorageKeySetting = $pipeworksManifest.Usertable.StorageKeySetting } $result = Invoke-WebCommand @extra -RunWithoutInput -PaymentProcessed -Command $postPaymentCommand -ParameterDefaultValue $PostPaymentParameter -AsEmail $userInfo.UserEmail -PlainOutput 2>&1 } $transactionExists | Add-Member NoteProperty Processed $true -Force -PassThru | Add-Member NoteProperty CommandResult ($result | Out-Html) -force -passthru | Add-Member NoteProperty PayPalIpnID $request['Transacation_Subject'] -Force -PassThru | Update-AzureTable -TableName $pipeworksManifest.UserTable.Name -Value { $_ } $smtpServer = $pipeworksManifest.UserTable.SmtpServer if ($smtpServer) { } } } else { New-Object PSObject -Property @{ Custom = $custom Errors = "$($error | Select-Object -First 1 | Out-String)" TransactionNotFound = $true Filter = $filterString } | Set-AzureTable -TableName $pipeworksManifest.UserTable.Name -RowKey { [GUID]::NewGuid() } -PartitionKey "PaypalIPN" } } elseif ($strResponse -eq "INVALID") { # //log for manual investigation $strResponse } else { # //log response/ipn data for manual investigation } } $facebookConfirmUser = { if (-not ($pipeworksManifest.Facebook.AppId -or $pipeworksManifest.Facebook.AppIdSetting)) { throw 'The Pipeworks manifest must include a facebook section with an AppId or AppIdSetting' return } $fbAppId = $pipeworksManifest.Facebook.AppId if (-not $fbAppId) { $fbAppId= Get-WebConfigurationSetting -Setting $pipeworksManifest.Facebook.AppIdSetting } if (-not $fbAppId) { throw "No Facebook AppID found" return } if ($request.Params["accesstoken"]) { $accessToken = $request.Params["accesstoken"] . Confirm-Person -FacebookAccessToken $accessToken -FacebookAppId $fbAppId -WebsiteUrl $finalUrl } elseif ($request.Params["code"]) { $code = $request.Params["code"] $fbSecret = Get-WebConfigurationSetting -Setting $pipeworksManifest.Facebook.AppSecretSetting $result =Get-Web -url "https://graph.facebook.com/oauth/access_token?client_id=$fbAppId&redirect_uri=$([Web.HttpUtility]::UrlEncode("${finalUrl}?FacebookConfirmed=true"))&client_secret=$fbsecret&code=$code" $token = [web.httputility]::ParseQueryString($result)["access_token"] . Confirm-Person -FacebookAccessToken $Token -FacebookAppId $fbAppId -WebsiteUrl $finalUrl } if ($request.Params["ReturnTo"]) { $returnUrl = [Web.HttpUtility]::UrlDecode($request.Params["ReturnTo"]) New-WebPage -AnalyticsId "" -title "Welcome to $($module.Name)" -RedirectTo $returnUrl | Out-HTML -WriteResponse } elseif ($Request.Params["ThenRun"]) { . $getCommandExtraInfo $Request.Params["ThenRun"] $result = Invoke-Webcommand -Command $command @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" -ServiceUrl $finalUrl 2>&1 } else { New-WebPage -AnalyticsId "" -title "Welcome to $($module.Name)" -RedirectTo "/" | Out-HTML -WriteResponse } } $liveIdConfirmUserHandler = { if ($request.Params["accesstoken"]) { $accessToken = $request.Params["accesstoken"] . Confirm-Person -liveIDAccessToken $accessToken -WebsiteUrl $finalUrl } elseif ($request.Params["code"]) { $code = $request.Params["code"] $appId = $pipeworksManifest.LiveConnect.ClientId $appSecret = Get-SecureSetting -Name $pipeworksManifest.LiveConnect.ClientSecretSetting -ValueOnly $redirectUri = if ($session["LiveIDRedirectURL"]) { $session["LiveIDRedirectURL"] } elseif ($pipeworksManifest.LiveConnect.RedirectUrl) { $pipeworksManifest.LiveConnect.RedirectUrl } else { $finalUrl } $result =Get-Web -url "https://login.live.com/oauth20_token.srf" -RequestBody "client_id=$([Web.HttpUtility]::UrlEncode($appId))&redirect_uri=$([Web.HttpUtility]::UrlEncode($redirectUri))&client_secret=$([Web.HttpUtility]::UrlEncode($appSecret.Trim()))&code=$([Web.HttpUtility]::UrlEncode($code.Trim()))&grant_type=authorization_code" -UseWebRequest -Method POST -AsJson $token = $result.access_token if (-not $Token) { New-Object PSObject -Property @{ Code = $code Error = ($Error |Out-String) AppId = $appId Result = $result RedirectUrl = $redirectUri } | Out-HTML -WriteResponse } else { if ($response.Cookies) { $confirmCookie = New-Object Web.HttpCookie ("LiveConnectCode_For_$($pipeworksManifest.LiveConnect.ClientID)", $code) $confirmCookie.Expires = (Get-Date).AddMinutes(15) $response.Cookies.Add($confirmCookie) } . Confirm-Person -LiveIDAccessToken $token -WebsiteUrl $finalUrl } } if ($session["User"]) { if ($request.Params["ReturnTo"]) { $returnUrl = [Web.HttpUtility]::UrlDecode($request.Params["ReturnTo"]) New-WebPage -AnalyticsId "" -title "Welcome to $($module.Name)" -RedirectTo $returnUrl | Out-HTML -WriteResponse } elseif ($Request.Params["ThenRun"]) { . $getCommandExtraInfo $Request.Params["ThenRun"] $result = Invoke-Webcommand -Command $command @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" -ServiceUrl $finalUrl 2>&1 if ($result) { $result | New-WebPage } } else { New-WebPage -AnalyticsId "" -title "Welcome to $($module.Name)" -RedirectTo "/" | Out-HTML -WriteResponse } } } #region Facebook Login Chunk $facebookLoginDisplay = { if (-not ($pipeworksManifest.Facebook.AppId -or $pipeworksManifest.Facebook.AppIdSetting)) { throw 'The Pipeworks manifest must include a facebook section with an AppId or AppIdSetting' return } $fbAppId = $pipeworksManifest.Facebook.AppId if (-not $fbAppId) { $fbAppId= Get-WebConfigurationSetting -Setting $pipeworksManifest.Facebook.AppIdSetting } if (-not $fbAppId) { throw "No Facebook AppID found" return } #if (-not $pipew $scope = if ($pipeworksManifest -and $pipeworksManifest.Facebook.Scope) { @($pipeworksManifest.Facebook.Scope) + "email" | Select-Object -Unique } else { "email" } $response.Write(("$(Write-Link -ToFacebookLogin -FacebookAppId $fbAppId -FacebookLoginScope $scope | New-WebPage -Title "Login with Facebook")")) } #endregion #region MailHandler $mailHandler = { $to = $request["To"] $from = $Request["From"] $replyTo = $request["Replyto"] $body = $Request["Body"] $subject= $Request["Subject"] $useSsl = -not $pipeworksManifest.Mail.DoNotUseSsl $smtpServer = $pipeworksManifest.Mail.SmtpServer $smtpUser = Get-WebConfigurationSetting -Setting $pipeworksManifest.Mail.SmtpUserSetting $smtpPassword = Get-WebConfigurationSetting -Setting $pipeworksManifest.Mail.SmtpPasswordSetting if (-not $pipeworksManifest.Mail.CanSendTo) { throw "Must add a CanSendTo list to the mail section" } $canSend = $false foreach ($couldSendTo in $pipeworksManifest.Mail.CanSendTo) { if ($to -like $couldSendto) { $canSend = $true } } if (-not $canSend) { throw "Cannot send mail to $to" } $smtpCred = New-Object Management.Automation.PSCredential ".\$smtpUser", (ConvertTo-SecureString -String $smtpPassword -AsPlainText -Force) $emailParams = @{ From=$from To=$To Body=$body+" ---- Reply To:$replyTo " Subject=$subject UseSsl=$useSsl SmtpServer=$smtpServer Credential=$smtpCred } Send-MailMessage @emailParams $redirectto = $Request["RedirectTo"] if ($redirectTo){ New-WebPage -RedirectTo $redirectTo | Out-HTML -WriteResponse } } #endregion MailHandler $tableItemProcess = { $BeginTableItem = { $itemsToShow = @() } $endTableItem = { $itemsToShow | Out-HTML -WriteResponse } $ProcessEachTableItem = { # If there's a content type, set the response's content type to match if ($_.RequireSessionHandShake -and -not $session[$_.RequireSessionHandShake]) { throw "Required Session Handshake is not present $($_.RequireSessionHandShake)" return } if ($_.ExpirationDate -and ( [DateTime]::Now -gt $_.ExpirationDate)) { throw "Content is Expired" return } # Unpack any properties on the item without spaces (or try) . $unpackItem if ($Request['LinkOnly']) { $item = Write-Link -Caption Name -Url $item.Url } if ($_.ContentType) { $response.ContentType = $_.ContentType } if ($_.Bytes) { $response.BufferOutput = $true $response.BinaryWrite([Convert]::FromBase64String($_.Bytes)) $response.Flush() } elseif ($_.Xml) { $strWrite = New-Object IO.StringWriter ([xml]($_.Xml)).Save($strWrite) $resultToOutput = "$strWrite" -replace "encoding=`"utf-16`"", "encoding=`"utf-8`"" if (-not $cmdOptions.ContentType) { $response.ContentType ="text/xml" } $response.Write("$resultToOutput") } elseif ($_.Html) { $itemsToShow += $_.Html } else { $itemsToShow += $_ } if ($_.TimesViewed) { $timesViewed = [int]$_.TimesViewed + 1 $putItBack = $_ $rowKey = $_.psobject.properties['RowKey'] $_.psobject.properties.Remove('RowKey') $partitionKey = $_.psobject.properties['PartitionKey'] $_.psobject.properties.Remove('PartitionKey') $tableName= $_.psobject.properties['TableName'] $_.psobject.properties.Remove('TableName') $putItBack | Add-Member NoteProperty TimesViewed $timesViewed -Force $putItBack | Update-AzureTable -TableName $tableName -PartitionKey $partitionKey -RowKey $rowKey } } } #region SearchHandler $SearchHandler = $embedUnpackItem + $tableItemProcess.ToString() + { if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) $lastSearchTime = $application["KeywordSearchTime_$($request['Search'])"] $lastResults = $application["SearchResults_$($request['Search'])"] # If the PipeworksManifest is going to index table data, then load this up rather than query if ($pipeworksManifest.Table.IndexBy) { if (-not $pipeworksManifest.Table.SqlAzureConnectionSetting) { Write-Error "Modules that index tables must also declare a SqlAzureConnectionString within the table" return } $connectionString = Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.SqlAzureConnectionSetting $sqlConnection = New-Object Data.SqlClient.SqlConnection "$connectionString" $sqlConnection.Open() $matchSql = @(foreach ($indexTerm in $pipeworksManifest.Table.IndexBy) { "$indexTerm like '%$($request['search'].Replace("'","''"))%'" }) -join ' or ' $searchSql = "select id from $($pipeworksManifest.Table.Name) where $matchSql" $sqlAdapter= new-object "Data.SqlClient.SqlDataAdapter" ($searchSql, $sqlConnection) $sqlAdapter.SelectCommand.CommandTimeout = 0 $dataSet = New-Object Data.DataSet $null = $sqlAdapter.Fill($dataSet) $allIds = @($dataSet.Tables | Select-Object -ExpandProperty Rows | Select-Object -ExpandProperty Id) foreach ($id in $allIds) { if (-not $id) { continue } $part,$row = $id -split ":" Get-AzureTable -TableName $pipeworksManifest.Table.Name -Row $row.Trim() -Partition $part.Trim() -StorageAccount $storageAccount -StorageKey $storageKey| ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } } else { Search-AzureTable -TableName $pipeworksManifest.Table.Name -Select Name, Description, Keyword, PartitionKey, RowKey -StorageAccount $storageAccount -StorageKey $storageKey | ForEach-Object $UnpackItem | Where-Object { ($_.Name -ilike "*$($request['Search'])*") -or ($_.Description -ilike "*$($request['Search'])*") -or ($_.Keyword -ilike "*$($request['Search'])*") -or ($_.Keywords -ilike "*$($request['Search'])*") } | Get-AzureTable -TableName $pipeworksManifest.Table.Name | ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } if (-not $lastResults) { if (-not $application['TableIndex'] -or (-not $pipeworksManifest.Table.IndexBy)) { # If theres' not an index, or the manifest does not build one, search the table $application['TableIndex'] = Search-AzureTable -TableName $pipeworksManifest.Table.Name -Select Name, Description, Keyword, PartitionKey, RowKey -StorageAccount $storageAccount -StorageKey $storageKey } } else { $lastResults | ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } } #endregion #region NameHandler $nameHandler = $tableItemProcess.ToString() + $embedUnpackItem + { if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) $nameMatch =([ScriptBLock]::Create("`$_.Name -eq '$($request['name'])'")) Search-AzureTable -Where $nameMatch -TableName $pipeworksManifest.Table.Name -StorageAccount $storageAccount -StorageKey $storageKey | ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } #endregion #region LatestHandler $latestHandler = $tableItemProcess.ToString() + $embedUnpackItem + { $PartitionKey = $request['Latest'] } + $refreshLatest + { $latest | ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } #endregion LatestHandler #region RssHandler $rssHandler = $embedUnpackItem + { $PartitionKey = $request['Rss'] } + $refreshLatest.ToString() + { if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) $finalSite = $finalUrl.ToString().Substring(0,$finalUrl.ToString().LastIndexOf("/")) $blogName = if ($pipeworksManifest.Blog.Name) { $pipeworksManifest.Blog.Name } else { $module.Name } $blogDescription = if ($pipeworksManifest.Blog.Description) { $pipeworksManifest.Blog.Description } else { $module.Description } $syncTime = [Datetime]::Now - [Timespan]"0:20:0" if (-not ($session["RssFeed$($blogName)LastSyncTime"] -ge $syncTime)) { $session["RssFeed$($blogName)"] = $null } if (-not $session["RssFeed$($blogName)"]) { $feedlength = if ($pipeworksManifest.Blog.FeedLength -as [int]) { $pipeworksManifest.Blog.FeedLength -as [int] } else { 25 } if ($feedLength -eq -1 ) { $feedLength = [int]::Max } $getDateScript = { if ($_.DatePublished) { [DateTime]$_.DatePublished } elseif ($_.TimeCreated) { [DateTime]$_.TimeCreated } elseif ($_.TimeGenerated) { [DateTime]$_.TimeGenerated } elseif ($_.Timestamp) { [DateTime]$_.Timestamp } else { Get-Date } } $rssFeed = Search-AzureTable -TableName $pipeworksManifest.Table.Name -Filter "PartitionKey eq '$PartitionKey'" -Select Timestamp, DatePublished, PartitionKey, RowKey -StorageAccount $storageAccount -StorageKey $storageKey | Sort-Object $getDateScript -Descending | Select-Object -First $feedlength | Get-AzureTable -TableName $pipeworksManifest.Table.Name | ForEach-Object $UnpackItem | New-RssItem -Title { if ($_.Name) { $_.Name } else { ' ' } } -DatePublished $getDateScript -Author { if ($_.Author) { $_.Author} else { ' ' } } -Url { if ($_.Url) { $_.Url } else { "$($finalSite.TrimEnd('/') + '/')?post=$($_.Name)" } } | Out-RssFeed -Title $blogName -Description $blogDescription -ErrorAction SilentlyContinue -Link $finalSite $session["RssFeed$($blogName)"] = $rssFeed $session["RssFeed$($blogName)LastSyncTime"] = Get-Date } else { $rssFeed = $session["RssFeed$($blogName)"] } $response.ContentType = 'text/xml' $response.Write($rssFeed) } #endregion RssHandler #region TypeHandler $typeHandler = $tableItemProcess.ToString() + $embedUnpackItem +{ if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) $nameMatch =([ScriptBLock]::Create("`$_.psTypeName -eq '$($request['Type'])'")) Search-AzureTable -Where $nameMatch -TableName $pipeworksManifest.Table.Name -StorageAccount $storageAccount -StorageKey $storageKey | ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } #endregion TypeHandler #region IdHandler $idHandler = $tableItemProcess.ToString() + $embedUnpackItem + { if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $partition, $row = $request['id'] -split ':' <#$rowMatch= [ScriptBLock]::Create("`$_.RowKey -eq '$row'") $partitionMatch = [ScriptBLock]::Create("`$_.PartitionKey -eq '$partition'") #> $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) Search-AzureTable -TableName $pipeworksManifest.Table.Name -StorageAccount $storageAccount -StorageKey $storageKey -Filter "RowKey eq '$row' and PartitionKey eq '$partition'" | ForEach-Object -Begin $BeginTableItem -Process $ProcessEachTableItem -End $EndTableItem } #endregion #region PrivacyPolicyHandler $privacyPolicyHandler = { $siteUrl = $finalUrl.ToString().Substring(0, $finalUrl.LastIndexOf("/")) + "/" $OrgInfo = if ($module.CompanyName) { $module.CompanyName } elseif ($pipeworksManifest.Organization.Name) { $pipeworksManifest.Organization.Name } else { " THE COMPANY" } $policy = if ($pipeworksManifest.PrivacyPolicy) { $pipeworksManifest.PrivacyPolicy } else { @" <!-- START PRIVACY POLICY CODE --> <div style="font-family:arial"> <strong>What information do we collect?</strong> <br /> <br /> We ( $OrgInfo ) collect information from you when you register on our site ( $siteUrl ) or place an order. <br /><br /> When ordering or registering on our site, as appropriate, you may be asked to enter your: name, e-mail address or phone number. You may, however, visit our site anonymously.<br /><br /> Google, as a third party vendor, uses cookies to serve ads on your site. Google's use of the DART cookie enables it to serve ads to your users based on their visit to your sites and other sites on the Internet. Users may opt out of the use of the DART cookie by visiting the Google ad and content network privacy policy..<br /><br /><strong>What do we use your information for?</strong><br /><br /> Any of the information we collect from you may be used in one of the following ways: <br /><br /> <br/> To personalize your experience<br /> (your information helps us to better respond to your individual needs)<br /><br /> <br/> To improve our website<br /> (we continually strive to improve our website offerings based on the information and feedback we receive from you)<br /><br /> <br/> To improve customer service<br /> (your information helps us to more effectively respond to your customer service requests and support needs)<br /><br /> <br/> To process transactions<br /><blockquote>Your information, whether public or private, will not be sold, exchanged, transferred, or given to any other company for any reason whatsoever, without your consent, other than for the express purpose of delivering the purchased product or service requested.</blockquote><br /> <br/>To send periodic emails<br /><blockquote>The email address you provide for order processing, may be used to send you information and updates pertaining to your order, in addition to receiving occasional company news, updates, related product or service information, etc.</blockquote><br /><br /><strong>How do we protect your information?</strong><br /><br /> We offer the use of a secure server. All supplied sensitive/credit information is transmitted via Secure Socket Layer (SSL) technology and then encrypted into our Payment gateway providers database only to be accessible by those authorized with special access rights to such systems, and are required to?keep the information confidential.<br /><br /> After a transaction, your private information (credit cards, social security numbers, financials, etc.) will not be stored on our servers.<br /><br /><strong>Do we use cookies?</strong><br /><br /> Yes (Cookies are small files that a site or its service provider transfers to your computers hard drive through your Web browser (if you allow) that enables the sites or service providers systems to recognize your browser and capture and remember certain information<br /><br /> We use cookies to help us remember and process the items in your shopping cart, understand and save your preferences for future visits and keep track of advertisements and .<br /><br /><strong>Do we disclose any information to outside parties?</strong><br /><br /> We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our website, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.<br /><br /><strong>Third party links</strong><br /><br /> Occasionally, at our discretion, we may include or offer third party products or services on our website. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.<br /><br /><strong>California Online Privacy Protection Act Compliance</strong><br /><br /> Because we value your privacy we have taken the necessary precautions to be in compliance with the California Online Privacy Protection Act. We therefore will not distribute your personal information to outside parties without your consent.<br /><br /><strong>Online Privacy Policy Only</strong><br /><br /> This online privacy policy applies only to information collected through our website and not to information collected offline.<br /><br /><strong>Your Consent</strong><br /><br /> By using our site, you consent to our web site privacy policy.<br /><br /><strong>Changes to our Privacy Policy</strong><br /><br /> If we decide to change our privacy policy, we will post those changes on this page. Hope this Helps, $OrgInfo "@ } $response.Write("$policy") return } #endregion #region Anything Handler $anythingHandler = $tableItemProcess.ToString() + $embedUnpackItem + { $anythingHandlerStartedAt = [DateTime]::Now # Determine the Relative Path, Full URL, and Depth # First, parse and chunk the full path, so we can see what to do with it # Create a Social Row (Facebook Likes, Google +1, Twitter) if (-not $script:SocialArea) { $script:SocialArea = " $( if (-not $antiSocial) { if ($pipeworksManifest -and ($pipeworksManifest.Facebook.AppId -or $pipeworksManifest.FacebookLike)) { Write-Link "facebook:like" } if ($pipeworksManifest -and ($pipeworksManifest.GoogleSiteVerification -or $pipeworksManifest.AddPlusOne)) { Write-Link "google:plusone" } if ($pipeworksManifest -and $pipeworksManifest.ShowTweet) { Write-Link "twitter:tweet" } elseif ($pipeworksManifest -and ($pipeworksManifest.TwitterId)) { Write-Link "twitter:tweet" Write-Link "twitter:follow@$($pipeworksManifest.TwitterId.TrimStart('@'))" } } ) " } $socialArea = $script:SocialArea $relativeUrlParts = @($relativeUrl.Split("/", [StringSplitOptions]"RemoveEmptyEntries")) $linkUrl = "$FinalUrl".Substring(0, "$FinalUrl".LastIndexOf("/")) $titleArea = if ($PipeworksManifest -and $pipeworksManifest.Logo) { "<a href='$linkUrl'><img src='$($pipeworksManifest.Logo)' alt='$($module)' style='border:0' /></a>" } else { "<a href='$linkUrl'>" + $Module.Name + "</a>" } $rest = "" # If there was more than ne additional part of the URL, it could be: # - A Command # - A Help Topic # - A Walkthru # - A Schema # - The user's profile if ($relativeUrlParts.Count -ge 1) { # If it's a command, invoke the command $found = $false $plainOutput = $false $part = $RelativeUrlParts[0] if ($module.ExportedFunctions.$part -or $module.ExportedAliases.$part -or $module.ExportedCmdlets.$part -or ( (-not $pipeworksManifest.IgnoreBuiltInCommand) -and $ExecutionContext.SessionState.InvokeCommand.GetCommand("$part", "Alias,Function,Cmdlet") )) { $found = $true if ($module.ExportedAliases.$part) { $command = $module.ExportedAliases.$part } elseif ($module.ExportedFunctions.$part) { $command = $module.ExportedFunctions.$part } elseif ($module.ExportedCmdlets.$part) { $command = $module.ExportedCmdlets.$part } elseif (-not $pipeworksManifest.IgnoreBuiltInCommand) { $command = @($ExecutionContext.SessionState.InvokeCommand.GetCommand("$part", "Alias,Function,Cmdlet"))[0] } # The display name of the command. By default, the alias or the command $displayedCommand = "$command" if ($command.ResolvedCommand) { $command = $command.ResolvedCommand } #$titleArea = $displayedCommand if ($command) { $found = $true } $commandDescription = "" # Cache the command help for performance $commandHelp = if (-not $script:CachedCommandHelp) { $script:CachedCommandHelp = @{} $script:CachedCommandHelp.$command = @(Get-Help $command -ErrorAction SilentlyContinue)[0] $script:CachedCommandHelp.$command } else { if (-not $script:CachedCommandHelp.$command) { $script:CachedCommandHelp.$command = @(Get-Help $command -ErrorAction SilentlyContinue)[0] } $script:CachedCommandHelp.$command } if ($commandHelp.Description) { $commandDescription = $commandHelp.Description[0].text $commandDescription = $commandDescription -replace "`n", ([Environment]::NewLine) } $Friendlyname = if ($pipeworksManifest.WebCommand.($command.Name).FriendlyName) { $pipeworksManifest.WebCommand.($command.Name).FriendlyName } else { $Command.Name } # Set the title variable, in the hopes that the templates will pick up on it later $title = $Friendlyname if (-not $script:CachedCommandDescription) { $script:CachedCommandDescription = @{} } if (-not $script:CachedCommandDescription.$Command) { $script:CachedCommandDescription.$Command = " <div style='font-size:1.11em;margin-top:1%;margin-left:3%;vertical-align:middle'> $Friendlyname <div style='font-size:.66em;margin-top:1%;margin-left:3%;vertical-align:middle'> $(ConvertFrom-Markdown -Markdown "$commandDescription ") </div> </div>" } $descriptionArea = $script:CachedCommandDescription.$Command $extraParams = if ($pipeworksManifest -and $pipeworksManifest.WebCommand.($Command.Name)) { @{} + $pipeworksManifest.WebCommand.($Command.Name) } elseif ($pipeworksManifest -and $pipeworksManifest.WebAlias.($Command.Name) -and $pipeworksManifest.WebCommand.($pipeworksManifest.WebAlias.($Command.Name).Command)) { $webAlias = $pipeworksManifest.WebAlias.($Command.Name) $paramBase = $pipeworksManifest.WebCommand.($pipeworksManifest.WebAlias.($Command.Name).Command) foreach ($kv in $webAlias.GetEnumerator()) { if (-not $kv) { continue } if ($kv.Key -eq 'Command') { continue } $paramBase[$kv.Key] = $kv.Value } @{} + $paramBase } else { # It's not a command we "trust", so display help @{ ShowHelp = $true } } if ($pipeworksManifest -and $pipeworksManifest.Style -and (-not $extraParams.Style)) { $extraParams.Style = $pipeworksManifest.Style } # Change the display name of the command has a friendly name if ($extraParams.FriendlyName) { $displayedCommand = $extraParams.FriendlyName } if ($extraParams.Count -gt 1) { # Very explicitly make sure it's there, and not explicitly false if (-not $extra.RunOnline -or $extraParams.Contains("RunOnline") -and $extaParams.RunOnline -ne $false) { $extraParams.RunOnline = $true } } if ($extaParams.PipeInto) { $extaParams.RunInSandbox = $true } if (-not $extraParams.AllowDownload) { $extraParams.AllowDownload = $allowDownload } if ($extraParams.RunOnline) { # Commands that can be run online $webCmds += $command.Name } if ($extraParams.RequireAppKey -or $extraParams.RequireLogin -or $extraParams.IfLoggedAs -or $extraParams.ValidUserPartition -or $extraParams.Cost -or $extraParams.CostFactor) { $extraParams.UserTable = $pipeworksManifest.UserTable.Name $extraParams.UserPartition = $pipeworksManifest.UserTable.Partition $extraParams.StorageAccountSetting = $pipeworksManifest.Usertable.StorageAccountSetting $extraParams.StorageKeySetting = $pipeworksManifest.Usertable.StorageKeySetting } if ($extraParams.AllowDownload) { # Downloadable Commands $downloadableCommands += $command.Name } if ($MarginPercentLeftString -and (-not $extraParams.MarginPercentLeft)) { $extraParams.MarginPercentLeft = $MarginPercentLeftString.TrimEnd("%") } if ($MarginPercentRightString-and -not $extraParams.MarginPercentRight) { $extraParams.MarginPercentRight = $MarginPercentRightString.TrimEnd("%") } if ($relativeUrlParts.Count -gt 1 ) { $commandMetaData = $command -as [Management.Automation.CommandMetadata] $hideParameter = if ($extraParams.HideParameter) { @($extraParams.HideParameter ) } else { @() } $allowedParameter = $CommandMetaData.Parameters.Keys # Remove the denied parameters $allParameters = foreach ($param in $allowedParameter) { if ($hideParameter -notcontains $param) { $param } } $order = @($allParameters| Select-Object @{ Name = "Name" Expression = { $_ } },@{ Name= "NaturalPosition" Expression = { $p = @($commandMetaData.Parameters[$_].ParameterSets.Values)[0].Position if ($p -ge 0) { $p } else { 1gb } } } | Where-Object { $_.NaturalPosition -ne 1gb } | Sort-Object NaturalPosition| Select-Object -ExpandProperty Name) $cmdPart, $orderedParams = @($relativeUrlParts | Where-Object { -not "$_".Contains("?") }) if (-not $extraParams.ParameterDefaultValue) { $extraParams.ParameterDefaultValue = @{} } # If there were ordered parameters, we can supply them via the URL (i.e. Measure-Sum/1/2/3/4/) if ($orderedParams) { $orderedParams = @($orderedParams) $lastParameter = $null for ($n =0 ;$n -lt $orderedParams.Count;$n++) { $ParameterValue = [Web.HttpUtility]::UrlDecode($orderedParams[$n]) if ($n -ge $order.Count) { $acceptsRemainingArguments = $command.Parameters.$($order[$order.Count -1]).Attributes | Where-Object {$_.ValueFromRemainingArguments } if ($acceptsRemainingArguments) { if ($command.Parameters.$($order[$order.Count -1]).ParameterType.IsSubclassOf([Array])) { $extraParams.ParameterDefaultValue.($order[$order.Count -1]) = @($extraParams.ParameterDefaultValue.($order[$order.Count -1])) + $parameterValue } elseif ($command.Parameters.$($order[$order.Count -1]).ParameterType -is [ScriptBlock]) { $extraParams.ParameterDefaultValue.($order[$order.Count -1]) = [ScriptBlock]::Create(($extraParams.ParameterDefaultValue.($order[$order.Count -1])).ToString() + $parameterValue) } else { $extraParams.ParameterDefaultValue.($order[$order.Count -1]) += $ParameterValue } } #$command.Parameter.$($order[$n]) } else { $extraParams.ParameterDefaultValue.($order[$n]) = $ParameterValue if ($command.Parameters.($order[$n]).ParameterType -eq [ScriptBlock]) { $extraParams.ParameterDefaultValue.($order[$n]) = [ScriptBlock]::Create($extraParams.ParameterDefaultValue.($order[$n])) } } if ($extraParams.ParameterDefaultValue.Count) { $extraParams.RunWithoutInput = $true } } } } if ($extraParams.DefaultParameter -and $extraParams.ParameterDefaultValue) { # Reconcile aliases $combinedTable = @{} foreach ($kv in $extraParams.ParameterDefaultValue.GetEnumerator()) { $combinedTable[$kv.Key] = $kv.Value } foreach ($kv in $extraParams.DefaultParameter.GetEnumerator()) { $combinedTable[$kv.Key] = $kv.Value } $null = $extraParams.Remove('DefaultParameter') $extraParams['ParameterDefaultValue'] = $combinedTable } $StartedInvokeWebCommand = [DateTime]::now # Run the command. $rest = $result = try { Invoke-Webcommand -Command $command @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" -ServiceUrl $finalUrl 2>&1 } catch { $_ } $TimeSpentInInvokeWebCommand = [DateTime]::now - $StartedGeneratingCoreContent if ($result) { if ($result -is [Management.Automation.ErrorRecord]) { $result = "$($result | Out-String -Width 10kb)" } elseif ($result -is [Exception]) { $result = "$($result | Out-String -Width 10kb)" } $ifTemplateFound = @{} if ($pipeworksManifest.TemplateFiles.$Command) { $ifTemplateFound.Template = $command } elseif ($pipeworksManifest.CommandTemplate) { $ifTemplateFound.Template =$pipeworksManifest.CommandTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } if ($Request.params["AsRss"] -or $Request.params["AsCsv"] -or $Request.params["AsXml"] -or $Request.Params["bare"] -or $extraParams.ContentType -or $extraParams.PlainOutput) { $realOrder = @() $navBarData = @{} $navBarOrder = @{} $navBarUrls = @{} $cmdTabs = @{} $cmdUrls = @{} if (((-not $extraParams.ContentType)) -or ( $extraParams.ContentType -notlike "text*" -and $result -is [string] ) -or ( $extraParams.ContentType -eq "text/html" -and $result -like "*class=?$($command)*" ) -or ( $result -is [string] -and $result -like "*class=?$($command)*" )) { # If it's not HTML or XML, but contains tags $rest = $result if ($request["inline"]) { $response.Write($result) } elseif ($request["Snug"] -or $request["Widget"]) { $outputPage = "<div style='clear:both;margin-top:1%'> </div>" + $result | New-Region -Style @{ "margin-left" = "3%" "margin-right" = "3%" }| New-WebPage -Title "$($module.Name) | $displayedCommand" $response.Write($outputPage) } else { if (-not $script:CachedNavBar.($module.Name)) { if (-not $script:CachedNavBar) { $script:CachedNavBar = @{} } . $getGroups -NavBarOnly $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } if ($cancacheGroups) { $script:CachedNavBar.($module.Name) = $navBarHtml } } else { $navBarHtml = $script:CachedNavBar.($module.Name) } . $getBanners " <div style='float:right;position:absolute;zindex:30;right:15px;top:15px;'> $socialArea </div> <div style='float:left'> <h1 style='float:left'>$titleArea</h1> <h2 style='text-align:right;float:left;margin-top:75px;margin-left:50px'> $descriptionArea </h2> $navBarHtml </div> $spacingDiv $spacingDiv $spacingDiv <div style='clear:both;margin-top:1%'>$upperBannerSlot</div> $rest <div style='clear:both;margin-top:1%'>$bottomBannerSlot</div> <div style='float:right;margin-top:15%'>$brandingSlot</div> " | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString } | New-WebPage -Title "$($module.Name) | $displayedCommand" @ifTemplateFound | Out-HTML -WriteResponse return } } else { $plainOutput = $true if ($result -and $extraParams.ContentType ) { if (( $result -is [string] -and $result -like "*class=?$($command)*" )-or ( $extraParams.ContentType -ne "text/html" -and $result -like "*class=?$($command)*" ) -or ( $extraParams.ContentType -notlike "text*" -and $result -is [string] )) { if (-not $script:CachedNavBar.($module.Name)) { if (-not $script:CachedNavBar) { $script:CachedNavBar = @{} } . $getGroups -NavBarOnly $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } if ($cancacheGroups) { $script:CachedNavBar.($module.Name) = $navBarHtml } } else { $navBarHtml = $script:CachedNavBar.($module.Name) } . $getBanners $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } New-WebPage -Title "$($module.Name) | $displayedCommand" @ifTemplateFound | Out-HTML -WriteResponse } else { $response.ContentType= $extraParams.ContentType if ($global:ContentDisposition) { $null = $response.Headers.Add('content-disposition', $global:ContentDisposition.ToString()) $global:ContentDisposition = $null } $response.Write("$result") } } elseif ($result) { $response.Write($result) } } } else { if (($result -is [Collections.IEnumerable]) -and ($result -isnot [string])) { $Result = $result | Out-HTML } $rest = $result if ($request["inline"]) { $response.Write($result) } elseif ($request["Snug"] -or $Request["Widget"]) { $outputPage = "<div style='clear:both;margin-top:1%'> </div>" + $result | New-Region -Style @{ "margin-left" = "3%" "margin-right" = "3%" }| New-WebPage -Title "$($module.Name) | $displayedCommand" $response.Write($outputPage) } else { $rest = $result $realOrder = @() $navBarData = @{} $navBarOrder = @{} $navBarUrls = @{} $cmdTabs = @{} $cmdUrls = @{} if (-not $script:CachedNavBar.($module.Name)) { if (-not $script:CachedNavBar) { $script:CachedNavBar = @{} } . $getGroups -NavBarOnly $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } if ($cancacheGroups) { $script:CachedNavBar.($module.Name) = $navBarHtml } } else { $navBarHtml = $script:CachedNavBar.($module.Name) } . $getBanners " " | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString } | New-WebPage -Title "$($module.Name) | $displayedCommand" @ifTemplateFound | Out-HTML -WriteResponse return } } } } elseif (($relativeUrlParts[0].EndsWith("-?")) -or ($relativeUrlParts[1] -eq '-?')) { $CommandNameGuess = $RelativeUrlParts[0].TrimEnd("?").TrimEnd("-") $command = $module.ExportedCommands[$commandNameGuess] if ($command) { $extraParams = if ($pipeworksManifest -and $pipeworksManifest.WebCommand.($Command.Name)) { @{} + $pipeworksManifest.WebCommand.($Command.Name) } else { @{} } $extraParams.ShowHelp = $true $result =Invoke-WebCommand -Command $command @extraParams -ServiceUrl $finalUrl 2>&1 } if ($result) { $result | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString }| New-WebPage -Title "$($module.Name) | $displayedCommand" @ifTemplateFound | Out-HTML -WriteResponse } return } $potentialTopicName = $relativeUrlParts[0].Replace("+"," ").Replace("_", " ").Replace("%20"," ") $potentialTopicName = [Regex]::Replace($potentialTopicName , "\b(\w)", { param($a) $a.Value.ToUpper() }) # If it's a topic, display the topic $theTopic = foreach ($top in $aboutTopics) { if ($top.Name -eq $potentialTopicName) { $top } } if ($theTopic) { $found = $true if (-not $script:CachedTopics) { $script:CachedTopics = @{} } $descriptionArea = "$potentialTopicName" $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } if (-not $script:CachedTopics["${Module}_${potentialTopicName}"]) { $script:CachedTopics["${Module}_${potentialTopicName}"] = ConvertFrom-Markdown -Markdown $theTopic.Topic -ScriptAsPowerShell -ShowData:$ShowDataInTopic } $rest = $script:CachedTopics["${Module}_${potentialTopicName}"] #$rest = ConvertFrom-Markdown -Markdown $theTopic.Topic -ScriptAsPowerShell -ShowData:$ShowDataInTopic if ($request["Snug"] -or $request["widget"]) { $response.Write("$rest") } $topicTemplateName = $potentialTopicName.Replace(" ", "_") } $theWalkthru = if ($walkthrus) { $walkthrus.GetEnumerator() | Where-Object { $_.Key -eq $potentialTopicName } } if ($theWalkthru) { $found = $true $descriptionArea = "$potentialTopicName" $params = @{} if ($pipeworksManifest.TrustedWalkthrus -contains $theWalkThru.Key) { $params['RunDemo'] = $true } if ($pipeworksManifest.WebWalkthrus -contains $theWalkThru.Key) { $params['OutputAsHtml'] = $true } if (-not $script:CachedTopics["${Module}_${potentialTopicName}"]) { $script:CachedTopics["${Module}_${potentialTopicName}"] = Write-WalkthruHTML -WalkthruName $theWalkthru.Key -WalkThru $theWalkthru.Value -StepByStep @params } $rest = $script:CachedTopics["${Module}_${potentialTopicName}"] if ($request["inline"]) { $response.Write("$rest") } elseif ($request["Snug"] -or $Request["widget"]) { $response.Write("$rest") } $topicTemplateName = $potentialTopicName.Replace(" ", "_") } if ($found -and $rest -and -not ($plainOutput) -and (-not $Request["Snug"]) -and (-not $request["widget"]) -and (-not $request["inline"])) { $ifTemplateFound = @{} if ($pipeworksManifest.TemplateFiles.$topicTemplateName) { $ifTemplateFound.Template =$topicTemplateName } elseif ($pipeworksManifest.TopicTemplate) { $ifTemplateFound.Template =$pipeworksManifest.TopicTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } if ($request["inline"]) { $response.Write($rest) } elseif ($request["Snug"]) { $socialArea + "<div style='clear:both;margin-top:1%'></div>" + ($spacingDiv * 4) + $rest | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString }| New-WebPage -Title "$($module.Name) | $potentialTopicName" | Out-HTML -WriteResponse } else { $realOrder = @() $navBarData = @{} $navBarOrder = @{} $navBarUrls = @{} $cmdTabs = @{} $cmdUrls = @{} if (-not $script:CachedNavBar.($module.Name)) { if (-not $script:CachedNavBar) { $script:CachedNavBar = @{} } . $getGroups -NavBarOnly $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } if ($cancacheGroups) { $script:CachedNavBar.($module.Name) = $navBarHtml } } else { $navBarHtml = $script:CachedNavBar.($module.Name) } . $getBanners $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } if ($ifTemplateFound.Template -and $ifTemplateFound.Template.Trim()) { $content = " <div style='float:right;position:absolute;zindex:30;right:15px;top:15px;'> $socialArea </div> <div style='float:left'> <h1 style='float:left'>$titleArea</h1> <h2 style='text-align:right;float:left;margin-top:75px;margin-left:50px'> $descriptionArea </h2> </div> " + ($spacingDiv * 3) + "<div style='clear:both;margin-top:1%'>$upperBannerSlot</div>" + $rest + "<div style='clear:both;margin-top:1%'>$bottomBannerSlot</div>" + "<div style='float:right;margin-top:15%'>$brandingSlot</div>" | New-Region -Style @{ "Margin-Left" = $marginPercentLeftString "Margin-Right" = $marginPercentLeftString } } else { $content = "" } "$content " | New-WebPage -Title "$($module.Name) | $potentialTopicName" @ifTemplateFound | Out-HTML -WriteResponse } } if (-not $found) { $thePage = $module | Split-Path | Get-ChildItem -Filter "Pages" -ErrorAction SilentlyContinue | Get-ChildItem -ErrorAction SilentlyContinue | Where-Object { '.html', '.htm', '.ps1', '.pspage' -contains $_.Extension }| Where-Object { $pageNAme = $_.Name.Substring(0, $_.Name.Length - $_.Extension.Length) ($pageName -eq $potentialTopicName) } if ($thePage) { $found = $true } if ($thePage.Extension -eq '.html' -or $thePage.Extension -eq '.htm') { $response.Write([IO.File]::ReadAllText($thePage.FullName)) } elseif ($thePage.Extension -eq '.ps1') { $responseContent = . $module $thePage.FullName $response.Write("$responseContent") } } # If there's not a topic, walkthru, command, or page # See if there's a schema. if ($pipeworksManifest.Schema -or $pipeworksManifest.Schemas) { $schemaList = if ($pipeworksManifest.Schema) { Import-PSData -Hashtable $pipeworksManifest.Schema } else { Import-PSData -Hashtable $pipeworksManifest.Schemas } $matchingSchema = $schemaList.$potentialTopicName if ($matchingSchema) { $ifTemplateFound = @{} if ($pipeworksManifest.TemplateFiles.$matchingSchema) { $ifTemplateFound.Template =$matchingSchema } if ($pipeworksManifest.TopicTemplate) { $ifTemplateFound.Template =$pipeworksManifest.TopicTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } $rest = $content = $matchingSchema | Out-HTML "$content " | New-WebPage -Title "$($module.Name) | $potentialTopicName" @ifTemplateFound | Out-HTML -WriteResponse $found = $true } } if ($potentialTopicName -eq 'Awards' -or $potentialTopicName -eq 'Award') { $awardsList = if ($pipeworksManifest.Award) { Import-PSData -Hashtable $pipeworksManifest.Award } elseif ($pipeworksManifest.Awards) { Import-PSData -Hashtable $pipeworksManifest.Awards } else { $null } $ifTemplateFound = @{} if ($pipeworksManifest.TemplateFiles.Awards) { $ifTemplateFound.Template ='Awards' } elseif ($pipeworksManifest.TemplateFiles.Award) { $ifTemplateFound.Template ='Award' } elseif ($pipeworksManifest.TopicTemplate) { $ifTemplateFound.Template =$pipeworksManifest.TopicTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } if ($awardsList) { $rest = ($awardsList.psobject.properties | Sort-Object Name | ForEach-Object { $_.Value | Out-HTML }) -join "<hr style='clear:both' />" "$rest" | New-WebPage -Title "$($module.Name) | $potentialTopicName" @ifTemplateFound | Out-HTML -WriteResponse $found = $true } } if ($potentialTopicName -eq 'Schemas' -or $potentialTopicName -eq 'Schema') { if ($pipeworksManifest.Schema -or $pipeworksManifest.Schemas) { $schemaList = if ($pipeworksManifest.Schema) { Import-PSData -Hashtable $pipeworksManifest.Schema } elseif ($pipeworksManifest.Schemas) { Import-PSData -Hashtable $pipeworksManifest.Schemas } else { $null } $ifTemplateFound = @{} if ($pipeworksManifest.TemplateFiles.Schemas) { $ifTemplateFound.Template ='Schemas' } elseif ($pipeworksManifest.TemplateFiles.Schema) { $ifTemplateFound.Template ='Schema' } elseif ($pipeworksManifest.TopicTemplate) { $ifTemplateFound.Template =$pipeworksManifest.TopicTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } if ($schemaList) { $rest = ($schemaList.psobject.properties | Sort-Object Name | ForEach-Object { $_.Value | Out-HTML }) -join "<hr style='clear:both' />" "$rest" | New-WebPage -Title "$($module.Name) | $potentialTopicName" @ifTemplateFound | Out-HTML -WriteResponse $found = $true } } } if ($potentialTopicName -eq 'Profile' -or $potentialTopicName -eq 'Me') { $ifTemplateFound = @{} if ($pipeworksManifest.TemplateFiles.Profile) { $ifTemplateFound.Template ='Profile' } elseif ($pipeworksManifest.TemplateFiles.Me) { $ifTemplateFound.Template ='Me' } if ($pipeworksManifest.TopicTemplate) { $ifTemplateFound.Template =$pipeworksManifest.TopicTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } $userName = "" $rest = if ($session -and $session["User"]) { $session["User"] | Out-HTML $userName = $session["User"].Name } else { $confirmed = Confirm-Person -WebsiteUrl $finalUrl if ($session -and $session["User"]) { $session["User"] | Out-HTML } else { $confirmed } } if ($request["inline"]) { $response.Write($rest) } elseif ($request["Snug"]) { "<div style='clear:both;margin-top:1%'> </div>" + $rest | New-Region -Style @{ "Margin-Left" = "1%" "Margin-Right" = "1%" }| New-WebPage -Title "$($module.Name) | $($Session["User"].Name)" | Out-HTML -WriteResponse } else { "$rest" | New-WebPage -Title "$($module.Name) | $userName" @ifTemplateFound | Out-HTML -WriteResponse } $found = $true } if (-not $found) { $response.StatusCode = 404 try { $response.TrySkipIisCustomErrors = $true } catch { } $response.Write("Not Found") return } } else { } $timeSpentInAnythingHandler = [DateTime]::Now - $anythingHandlerStartedAt } #endregion Anything Handler #region ObjectHandler $objectHandler = { if (-not ($pipeworksManifest.Table -and $pipeworksManifest.Table.StorageAccountSetting -and $pipeworksManifest.Table.StorageKeySetting)) { throw 'The Pipeworks manifest must include three settings in order to retrieve items from table storage: Table, TableStorageAccountSetting, and TableStorageKeySetting' return } $partition, $row = $request['Object'] -split ':' $rowMatch= [ScriptBLock]::Create("`$_.RowKey -eq '$row'") $partitionMatch = [ScriptBLock]::Create("`$_.PartitionKey -eq '$partition'") $storageAccount = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageAccountSetting) $storageKey = (Get-WebConfigurationSetting -Setting $pipeworksManifest.Table.StorageKeySetting) Show-WebObject -Table $pipeworksManifest.Table.Name -Row $row -Part $partition | New-Region -Style @{ 'margin-left' = '7.5%' 'margin-right' = '7.5%' 'margin-top' = '2%' } -layerid objectHolder | New-WebPage -Title $row | Out-HTML -WriteResponse } #endregion ObjectHandler # The Import Handler $importHandler = { $returnedScript = { }.ToString() + @" `$moduleName = '$($module.Name)' if (Get-Module `$moduleName) { Write-Warning '$($module.Name) Already Exists' return } `$xhttp = New-Object -ComObject Microsoft.XmlHttp `$xhttp.open('GET', '${finalUrl}?-GetManifest', 1) `$xhttp.Send() do { Write-Progress "Downloading Manifest" '${finalUrl}?-GetManifest' } while (`$xHttp.ReadyState -ne 4) `$manifest = `$xHttp.ResponseText if (-not `$toDirectory) { `$targetModuleDirectory =Join-Path `$home '\Documents\WindowsPowerShell\Modules\$($module.Name)' } else { `$targetModuleDirectory = `$toDirectory } Write-Progress "Downloading Commands" "${finalUrl}?-GetManifest" "@ + { $importScript = $manifest | Select-Xml //AllCommands | ForEach-Object { $_.Node.Command } | ForEach-Object -Begin { $stringBuilder = New-Object Text.StringBuilder } { $cmdName = $_.Name Write-Progress "Downloading Metadata" "$cmdName" $xhttp.open('GET', "$($_.Url.Trim('/'))/?-GetMetaData", 1) $xhttp.Send() do { Write-Progress "Downloading Metadata" "$cmdName" } while ($xHttp.ReadyState -ne 4) $commandMetaData = $xHttp.responseText $cxml = $commandMetaData -as [xml] if ($cxml.CommandManifest.AllowDownload -eq 'true') { # Download it $xhttp.open('GET', "$($_.Url.TrimEnd('/'))/?-Download", 1) $xhttp.Send() do { Write-Progress "Downloading" "$cmdName" } while ($xHttp.ReadyState -ne 4 ) try { $sb = $xHttp.responseText $null = ([ScriptBlock]::Create($sb)) $null = $stringBuilder.Append("$sb ") } catch { Write-Debug $xHttp.ResponseText $_ | Write-Error } } elseif ($cxml.CommandManifest.RunOnline -eq 'true') { # Download the proxy $xhttp.open('GET', "$($_.Url.TrimEnd('/'))/?-DownloadProxy", 1) $xhttp.Send() do { Write-Debug "Downloading" "$cmdName" } while ($xHttp.ReadyState -ne 4) $sb = $xHttp.responseText . ([ScriptBlock]::Create($sb)) if ($?) { $null = $stringBuilder.Append("$sb ") } } } -End { [ScriptBLock]::Create($stringBuilder) } New-Module -ScriptBlock $importScript -Name $moduleName } $response.ContentType = 'text/plain' $response.Write("$returnedScript") $response.Flush() } # The Self-Install Handler $installMeHandler = { $returnedScript = { param([string]$toDirectory) $webClient = New-Object Net.WebClient }.ToString() + @" Write-Progress "Downloading Manifest" '${finalUrl}?-GetManifest' `$manifest = `$webClient.DownloadString('${finalUrl}?-GetManifest') if (-not `$toDirectory) { `$targetModuleDirectory =Join-Path `$home '\Documents\WindowsPowerShell\Modules\$($module.Name)' } else { `$targetModuleDirectory = `$toDirectory } "@ + { Write-Progress "Downloading Commands" '${finalUrl}?-GetManifest' if ((Test-Path $targetModuleDirectory) -and (-not $toDirectory)) { Write-Warning "$targetModuleDirectory Exists, Creating ${targetModuleDirectory}Proxy" $targetModuleDirectory = "${targetModuleDirectory}Proxy" } $null = New-Item -ItemType Directory -Path $targetModuleDirectory $directoryName = Split-Path $targetModuleDirectory -Leaf $xmlMan = $manifest -as [xml] $moduleVersion = $xmlMan.ModuleManifest.Version -as [Version] if (-not $moduleVersion) { $moduleVersion = "0.0" } $guidLine = if ($xmlMan.ModuleManifest.Guid) { "Guid = '$($xmlMan.ModuleManifest.Guid)'" } else { ""} $companyLine = if ($xmlMan.ModuleManifest.Company) { "CompanyName = '$($xmlMan.ModuleManifest.Company)'" } else { ""} $authorLine = if ($xmlMan.ModuleManifest.Author) { "Author = '$($xmlMan.ModuleManifest.Author)'" } else { ""} $CopyrightLine = if ($xmlMan.ModuleManifest.Copyright) { "Copyright = '$($xmlMan.ModuleManifest.Copyright)'" } else { ""} $descriptionLine= if ($xmlMan.ModuleManifest.Description) { "Description = @' $($xmlMan.ModuleManifest.Description) '@" } else { ""} $psd1 = @" @{ ModuleVersion = '$($moduleVersion)' ModuleToProcess = '${directoryName}.psm1' $descriptionLine $guidLine $companyLine $authorLine $CopyrightLine PrivateData = @{ Url = '$($xmlMan.ModuleManifest.Url)' XmlManifest = @' $( $strWrite = New-Object IO.StringWriter $xmlMan.Save($strWrite) $strWrite ) '@ } } "@ $psm1 = $manifest | Select-Xml //AllCommands | ForEach-Object { $_.Node.Command } | ForEach-Object -Begin { $psm1 = "" } { $targetPath = Join-Path $targetModuleDirectory "$($_.Name).ps1" Write-Progress "Downloading $($_.Name)" "From $($_.Url) to $targetPath" $commandMetaData = $webClient.DownloadString("$($_.Url.Trim('/'))/?-GetMetaData") $cxml = $commandMetaData -as [xml] if ($cxml.CommandManifest.AllowDownload -eq 'true') { # Download it $webClient.DownloadString("$($_.Url.Trim('/'))/?-AllowDownload") | Set-Content $targetPath } elseif ($cxml.CommandManifest.RunOnline -eq 'true') { # Download the proxy $webClient.DownloadString("$($_.Url.Trim('/'))/?-DownloadProxy") | Set-Content $targetPath } else { # Download the stub $webClient.DownloadString("$($_.Url.Trim('/'))/?-Stub") | Set-Content $targetPath } $psm1 += '. $psScriptRoot\' + $_.Name + '.ps1' + ([Environment]::NewLine) } -End { $psm1 } $psm1 | Set-Content "$targetModuleDirectory\$directoryName.psm1" $psd1 | Set-Content "$targetModuleDirectory\$directoryName.psd1" } $response.ContentType = 'text/plain' $response.Write("$returnedScript") $response.Flush() } # all Commands page $allCommandsPage = { $commandUrlList= Get-Command | Where-Object { $_.Module.Name -eq $module.Name } | Sort-Object | Select-Object -ExpandProperty Name | Write-Link -List $order = @() $layerTitle = "$($module.Name) | All Commands" $order += $layerTitle $layers = @{ $layerTitle = '<div style=''margin-left:15px''>' + $commandUrlList+ '</div>' } # Group by Verb Get-Command | Where-Object { $_.Module.Name -eq $module.Name } | Group-Object {$_.Name.Substring(0,$_.Name.IndexOf("-")) } | ForEach-Object { $order += $_.Name $layers[$_.Name] = '<div style=''margin-left:15px''>' + ($_.Group | Select-Object -ExpandProperty Name | Write-Link) + '</div>' } $region = New-Region -AutoSwitch '0:0:15' -HorizontalRuleUnderTitle -DefaultToFirst -Order $order -Container 'CommandList' -Layer $layers $page = New-WebPage -Css $cssStyle -Title $layerTitle -AnalyticsID '$analyticsId' -PageBody $region $response.ContentType = 'text/html' $response.Write(" $page ") } # -GetCommand list $getCommandList = { $baseUrl = $request.URL $commandUrlList = foreach ($cmd in $moduleCommands) { $cmd.Name } $commandUrlList = $commandUrlList | Sort-Object $response.ContentType = 'text/plain' $commandList = ($commandUrlList -join ([Environment]::NewLine)) $response.Write([string]" $commandList ") } $newRobotsTxt = { param($RemoteCommandUrl) "User-Agent: * Crawl-Delay: 5 Host: $RemoteCommandUrl " } $NewSiteMap = { param($RemoteCommandUrl) $aboutFiles = @(Get-ChildItem -Filter *.help.txt -Path "$moduleRoot\en-us" -ErrorAction SilentlyContinue) if ($requestCulture -and ($requestCulture -ine 'en-us')) { $aboutFiles += @(Get-ChildItem -Filter *.help.txt -Path "$moduleRoot\$requestCulture" -ErrorAction SilentlyContinue) } $walkThrus = @{} $aboutTopics = @() $namedTopics = @{} $aboutTopicsByName = @{} if ($aboutFiles) { foreach ($topic in $aboutFiles) { if ($topic.fullname -like "*.walkthru.help.txt") { $topicName = $topic.Name.Replace('_',' ').Replace('.walkthru.help.txt','') $walkthruContent = Get-Walkthru -File $topic.Fullname $walkThruName = $topicName $walkThrus[$walkThruName] = $walkthruContent } else { $topicName = $topic.Name.Replace(".help.txt","") $nat = New-Object PSObject -Property @{ Name = $topicName.Replace("_", " ") SystemName = $topicName Topic = [IO.File]::readalltext($topic.Fullname) LastWriteTime = $topic.LastWriteTime } $aboutTopics += $nat $aboutTopicsByName[$nat.Name] = $nat } } } $blogChunk = if ($pipeworksManifest.Blog -and $pipeworksManifest.Blog.Link -and $pipeworksManifest.Blog.Name) { $blogLink = if ($pipeworksManifest.Blog.Link -like "http*" -and $pipeworksManifest.Blog.Name -and $pipeworksManifest.Blog.Description) { # Absolute link to module base, $pipeworksManifest.Blog.Link.TrimEnd("/") + "/Module.ashx?rss=$($pipeworksManifest.Blog.Name)" } else { $pipeworksManifest.Blog.Link } "<url> <loc>$([Security.Securityelement]::Escape($BlogLink))</loc> </url>" } else { "" } $aboutChunk = "" $aboutChunk = foreach ($topic in $aboutTopics) { if (-not $topic) { continue } $isInGroup = $false if (($pipeworksManifest.TopicGroup | Where-Object { $_.Values | Where-Object { $_ -eq ($Topic.Name) } }) -or ($pipeworksManifest.Group | Where-Object { $_.Values | Where-Object { $_ -eq ($Topic.Name) } })) { $isInGroup = $true } "<url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($topic.Name)/</loc> <changefreq>weekly</changefreq> $(if ($isInGroup) { "<priority>0.8</priority>" }) </url>" } $walkthruChunk = "" $walkthruChunk = foreach ($walkthruName in ($walkthrus.Keys | Sort-Object)) { $isInGroup = $false if (($pipeworksManifest.TopicGroup | Where-Object { $_.Values | Where-Object { $_ -eq $walkthruName } }) -or ($pipeworksManifest.Group | Where-Object { $_.Values | Where-Object { $_ -eq $walkthruName } })) { $isInGroup = $true } "<url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($walkthruName)/</loc> <changefreq>weekly</changefreq> $(if ($isInGroup) { "<priority>0.8</priority>" }) </url>" } $CommandChunk = "" $CommandChunk = foreach ($cmd in ($pipeworksManifest.WebCommand.Keys | Sort-Object)) { if ($pipeworksManifest.WebCommand[$Cmd].Hidden -or $pipeworksManifest.WebCommand[$Cmd].IfLoggedInAs -or $pipeworksManifest.WebCommand[$Cmd].ValidUserPartition -or $pipeworksManifest.WebCommand[$Cmd].RequireLogin -or $pipeworksManifest.WebCommand[$Cmd].RequireAppKey) { continue } $aliased = Get-Command -Module $module -CommandType Alias | Where-Object { $_.ResolvedCommand.Name -eq $cmd } $isInGroup = $false if (($pipeworksManifest.Group | Where-Object { $_.Values | Where-Object { $_ -eq $cmd } })) { $isInGroup = $true } if ($aliased) { foreach ($a in $aliased) { " <url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($a.Name)/</loc> $(if ($isInGroup) { "<priority>1</priority>" } else { "<priority>0.7</priority>" }) </url> " } } "<url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($cmd)/</loc> $(if ($isInGroup) { "<priority>0.9</priority>" } else { "<priority>0.6</priority>" }) </url>" } $siteMapXml = [xml]"<urlset xmlns=`"http://www.sitemaps.org/schemas/sitemap/0.9`"> <url> <loc>$($remoteCommandUrl.TrimEnd('/'))/</loc> <priority>1.0</priority> </url> $aboutChunk $WalkThruChunk $CommandChunk </urlset>" return $siteMapXml } $getSiteMapHandler = { if ($application -and $application["SitemapFor_$($module.Name)"]) { $manifestXml = $application["SitemapFor_$($module.Name)"] $strWrite = New-Object IO.StringWriter $manifestXml.Save($strWrite) $response.ContentType = 'text/xml' $response.Write("$strWrite") return } $blogChunk = if ($pipeworksManifest.Blog -and $pipeworksManifest.Blog.Link -and $pipeworksManifest.Blog.Name) { $blogLink = if ($pipeworksManifest.Blog.Link -like "http*" -and $pipeworksManifest.Blog.Name -and $pipeworksManifest.Blog.Description) { # Absolute link to module base, $pipeworksManifest.Blog.Link.TrimEnd("/") + "/Module.ashx?rss=$($pipeworksManifest.Blog.Name)" } else { $pipeworksManifest.Blog.Link } "<url> <loc>$([Security.Securityelement]::Escape($BlogLink))</loc> </url>" } else { "" } $aboutChunk = "" $aboutChunk = foreach ($topic in $aboutTopics) { if (-not $topic) { continue } $isInGroup = $false if (($pipeworksManifest.TopicGroup | Where-Object { $_.Values | Where-Object { $_ -eq ($Topic.Name) } }) -or ($pipeworksManifest.Group | Where-Object { $_.Values | Where-Object { $_ -eq ($Topic.Name) } })) { $isInGroup = $true } "<url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($topic.Name)</loc> <changefreq>weekly</changefreq> $(if ($isInGroup) { "<priority>0.8</priority>" }) </url>" } $walkthruChunk = "" $walkthruChunk = foreach ($walkthruName in ($walkthrus.Keys | Sort-Object)) { $isInGroup = $false if (($pipeworksManifest.TopicGroup | Where-Object { $_.Values | Where-Object { $_ -eq $walkthruName } }) -or ($pipeworksManifest.Group | Where-Object { $_.Values | Where-Object { $_ -eq $walkthruName } })) { $isInGroup = $true } "<url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($walkthruName)</loc> <changefreq>weekly</changefreq> $(if ($isInGroup) { "<priority>0.8</priority>" }) </url>" } $CommandChunk = "" $CommandChunk = foreach ($cmd in ($pipeworksManifest.WebCommand.Keys | Sort-Object)) { if ($pipeworksManifest.WebCommand[$Cmd].Hidden -or $pipeworksManifest.WebCommand[$Cmd].IfLoggedInAs -or $pipeworksManifest.WebCommand[$Cmd].ValidUserPartition -or $pipeworksManifest.WebCommand[$Cmd].RequireLogin -or $pipeworksManifest.WebCommand[$Cmd].RequireAppKey) { continue } $aliased = Get-Command -Module $module -CommandType Alias | Where-Object { $_.ResolvedCommand.Name -eq $cmd } $isInGroup = $false if (($pipeworksManifest.Group | Where-Object { $_.Values | Where-Object { $_ -eq $cmd } })) { $isInGroup = $true } if ($aliased) { foreach ($a in $aliased) { " <url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($a.Name)</loc> $(if ($isInGroup) { "<priority>1</priority>" } else { "<priority>0.7</priority>" }) </url> " } } "<url> <loc>$($remoteCommandUrl.TrimEnd('/'))/$($cmd)</loc> $(if ($isInGroup) { "<priority>0.9</priority>" } else { "<priority>0.6</priority>" }) </url>" } $siteMapXml = [xml]"<urlset xmlns=`"http://www.sitemaps.org/schemas/sitemap/0.9`"> <url> <loc>$($remoteCommandUrl.TrimEnd('/'))/</loc> <priority>1.0</priority> </url> $aboutChunk $WalkThruChunk $CommandChunk </urlset>" $application["SitemapFor_$($module.Name)"] = $siteMapXml; $strWrite = New-Object IO.StringWriter $siteMapXml.Save($strWrite) $response.ContentType = 'text/xml' $response.Write("$strWrite") } $getManifestXmlHandler = { if ($application -and $application["ManifestXmlFor_$($module.Name)"]) { $manifestXml = $application["ManifestXmlFor_$($module.Name)"] $strWrite = New-Object IO.StringWriter $manifestXml.Save($strWrite) $response.ContentType = 'text/xml' $response.Write("$strWrite") return } # The Manifest XML is used to help interact with a module from a remote service. # It contains module metadata and discovery information that will be used by most clients. $commandGroupChunk = "" $commandGroupChunk = foreach ($commandGroup in $pipeworksmanifest.Group) { if (-not $commandGroup) { continue } if ($commandGroup -isnot [Hashtable]) { continue } foreach ($kv in $commandGroup.GetEnumerator()) { $groupItems = $null $groupItems= foreach ($cmd in $kv.Value) { if ($pipeworksManifest.WebCommand.$cmd) { "<Command>$cmd</Command>" } } if ($groupItems) { "<CommandGroup> <Name> $($kv.Key) </Name> $groupItems </CommandGroup>" } } $cmdGroups } if ($commandGroupChunk) { $commandGroupChunk = "<CommandGroups> $commandGroupChunk </CommandGroups>" } $topicGroupChunk = "" $topicGroupChunk = foreach ($topicGroup in $pipeworksmanifest.Group) { if (-not $topicGroup) { continue } if ($topicGroup -isnot [Hashtable]) { continue } foreach ($kv in $topicGroup.GetEnumerator()) { $groupItems= foreach ($cmd in $kv.Value) { if (-not (Get-Command -Module $module -Name $Cmd -ErrorAction SilentlyContinue)) { "<Topic>$cmd</Topic>" } } if ($groupItems) { "<TopicGroup> <Name> $($kv.Key) </Name> $groupItems </TopicGroup>" } } } if ($topicGroupChunk ) { $topicGroupChunk = "<TopicGroups> $topicGroupChunk </TopicGroups>" } $blogChunk = if ($pipeworksManifest.Blog -and $pipeworksManifest.Blog.Link -and $pipeworksManifest.Blog.Name) { $blogLink = if ($pipeworksManifest.Blog.Link -like "http*" -and $pipeworksManifest.Blog.Name -and $pipeworksManifest.Blog.Description) { # Absolute link to module base, $pipeworksManifest.Blog.Link.TrimEnd("/") + "/Module.ashx?rss=$($pipeworksManifest.Blog.Name)" } else { $pipeworksManifest.Blog.Link } "<Blog> <Name>$([Security.Securityelement]::Escape($pipeworksManifest.Blog.Name))</Name> <Feed>$([Security.Securityelement]::Escape($BlogLink))</Feed> </Blog>" } else { "" } $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } $aboutChunk = foreach ($topic in $aboutTopics) { if (-not $topic) { continue } "<Topic> <Name> $([Security.Securityelement]::Escape($topic.Name)) </Name> <Content> $([Security.Securityelement]::Escape(( ConvertFrom-Markdown -Markdown $topic.Topic -ScriptAsPowerShell -ShowData:$ShowDataInTopic ))) </Content> </Topic>" } $styleChunk = if ($pipeworksmanifest.Style) { $styleXml = "<Style>" if ($pipeworksmanifest.Style.Body."font-family") { $fonts = foreach ($fontName in ($pipeworksmanifest.Style.Body."font-family" -split ",")) { "<Font>$([Security.SecurityElement]::Escape($FontName))</Font>" } $styleXml += "<Fonts>$Fonts</Fonts>" } if ($pipeworksmanifest.Style.Body.color) { $styleXml += "<Foreground>$([Security.SecurityElement]::Escape($pipeworksmanifest.Style.Body.color))</Foreground>" } if ($pipeworksmanifest.Style.Body.'background-color') { $styleXml += "<Background>$([Security.SecurityElement]::Escape($pipeworksmanifest.Style.Body.'background-color'))</Background>" } $styleXml += "</Style>" $styleXml } else { "" } $walkthruChunk = foreach ($walkthruName in ($walkthrus.Keys | Sort-Object)) { $steps = foreach ($step in $walkthrus[$walkthruName]) { $videoChunk = if ($step.videoFile) { "<Video>$($step.videoFile)</Video>" } else { "" } "<Step> <Explanation> $([Security.SecurityElement]::Escape($step.Explanation)) </Explanation> <Script> $( if ($Step.Script -ne '$null') { [Security.SecurityElement]::Escape((Write-ScriptHTML -Text $step.Script)) }) </Script> $videoChunk </Step>" } "<Walkthru> <Name>$([Security.SecurityElement]::Escape($WalkthruName))</Name> $steps </Walkthru>" } if ($aboutChunk -or $walkthruChunk) { $aboutChunk = " <About> $aboutChunk $WalkthruChunk </About> " } # This handler creates a Manifest XML. $psd1Content = (Get-Content $psd1Path -ReadCount 0 -ErrorAction SilentlyContinue) $psd1Content = $psd1Content -join ([Environment]::NewLine) $manifestObject= New-Object PSObject (& ([ScriptBlock]::Create( $psd1Content ))) $protocol = ($request['Server_Protocol'] -split '/')[0] $serverName= $request['Server_Name'] $shortPath = Split-Path $request['PATH_INFO'] $remoteCommandUrl= $Protocol + '://' + $ServerName.Replace('\', '/').TrimEnd('/') + '/' + $shortPath.Replace('\','/').TrimStart('/') $remoteCommandUrl = ($finalUrl -replace 'Module\.ashx', "" -replace 'Default.ashx', "").TrimEnd("/") $pipeworksManifestPath = Join-Path (Split-Path $module.Path) "$($module.Name).Pipeworks.psd1" $pipeworksManifest = if (Test-Path $pipeworksManifestPath) { try { & ([ScriptBlock]::Create( "data -SupportedCommand Add-Member, New-WebPage, New-Region, Write-CSS, Write-Ajax, Out-Html, Write-Link { $( [ScriptBlock]::Create([IO.File]::ReadAllText($pipeworksManifestPath)) )}")) } catch { Write-Error "Could not read pipeworks manifest: ($_ | Out-String)" } } else { $null } $allCommandChunks = New-Object Text.StringBuilder $cmdsInModule = Get-Command | Where-Object { $_.Module.Name -eq $module.Name } foreach ($cmd in $cmdsInModule) { $help = Get-Help $cmd.Name if ($help.Synopsis) { $description = $help.Synopsis $null = $allCommandChunks.Append("<Command Name='$($cmd.Name)' Url='$($remoteCommandUrl.TrimEnd('/') + '/' + $cmd.Name + '/')'>$([Security.SecurityElement]::Escape($description))</Command>") } else { $null = $allCommandChunks.Append("<Command Name='$($cmd.Name)' Url='$($remoteCommandUrl.TrimEnd('/') + '/' + $cmd.Name + '/')'/>") } } $allCommandChunks = "$allCommandChunks" $defaultCommmandChunk = if ($pipeworksManifest.DefaultCommand) { $defaultParams = if ($pipeworksManifest.DefaultCommand.Parameter) { foreach ($kv in ($pipeworksManifest.DefaultCommand.Parameter | Sort-Object Key)) { " <Parameter> <Name>$([Security.SecurityElement]::Escape($kv.Key))</Name> <Value>$([Security.SecurityElement]::Escape($kv.Value))</Value> </Parameter> " } } else { "" } "<DefaultCommand> <Name>$($pipeworksManifest.DefaultCommand.Name)</Name> $defaultParams </DefaultCommand>" } else { "" } if ($pipeworksManifest.WebCommand) { $webCommandsChunk = "<WebCommand>" $webcommandOrder = if ($pipeworksManifest.CommandOrder) { $pipeworksManifest.CommandOrder } else { $pipeworksManifest.WebCommand.Keys | Sort-Object } foreach ($wc in $webcommandOrder) { $LoginRequired= $pipeworksManifest.WebCommand.$($wc).RequireLogin -or $pipeworksManifest.WebCommand.$($wc).RequiresLogin -or $pipeworksManifest.WebCommand.$($wc).RequireAppKey -or $pipeworksManifest.WebCommand.$($wc).RequiresAppKey -or $pipeworksManifest.WebCommand.$($wc).IfLoggedInAs -or $pipeworksManifest.WebCommand.$($wc).ValidUserPartition $LoginRequiredChunk = if ($loginRequired) { " RequireLogin='true'" } else { "" } $isHiddenChunk = if ($pipeworksManifest.WebCommand.$($wc).IfLoggedInAs -or $pipeworksManifest.WebCommand.$($wc).ValidUserPartition -or $pipeworksManifest.WebCommand.$($wc).Hidden) { " Hidden='true'" } else { " " } $cmdFriendlyName = if ($pipeworksManifest.WebCommand.$wc.FriendlyName) { $pipeworksManifest.WebCommand.$wc.FriendlyName } else { $wc } $runWithoutInputChunk = if ($pipeworksManifest.WebCommand.$($wc).RunWithoutInput) { " RunWithoutInput='true'" } else { "" } $selectivelyVisibleChunk = if ($pipeworksManifest.WebCommand.$($wc).IfLoggedInAs -or $pipeworksManifest.WebCommand.$($wc).ValidUserPartition) { " SelectivelyVisible='true'" } else { "" } $redirectToChunk = if ($pipeworksManifest.WebCommand.$($wc).RedirectTo) { " RedirectTo='$([web.httputility]::HtmlAttributeEncode($pipeworksManifest.WebCommand.$($wc).RedirectTo))'" } else { "" } $redirectInChunk = if ($pipeworksManifest.WebCommand.$($wc).RedirectIn) { " RedirectIn='$([web.httputility]::HtmlAttributeEncode($pipeworksManifest.WebCommand.$($wc).RedirectIn))'" } elseif ($pipeworksManifest.WebCommand.$($wc).RedirectTo) { " RedirectIn='$([web.httputility]::HtmlAttributeEncode("00:00:00.25"))'" } else { "" } $help = Get-Help $wc if ($help.Synopsis) { $description = $help.Synopsis $webCommandsChunk += "<Command Name='$([Security.SecurityElement]::Escape($cmdFriendlyName))' RealName='$wc' ${LoginRequiredChunk}${isHiddenChunk}${runWithoutInputChunk}${selectivelyVisibleChunk} Url='$($remoteCommandUrl.TrimEnd('/') + '/' + $wc + '/')' $redirectToChunk $redirectInChunk>$([Security.SecurityElement]::Escape($description))</Command>" } else { $webCommandsChunk += "<Command Name='$([Security.SecurityElement]::Escape($cmdFriendlyName))' RealName='$wc' ${LoginRequiredChunk}${isHiddenChunk}${runWithoutInputChunk}${selectivelyVisibleChunk} Url='$($remoteCommandUrl.TrimEnd('/') + '/' + $wc + '/')' $redirectToChunk $redirectInChunk />" } } $webCommandsChunk += "</WebCommand>" } if ($pipeworksManifest.ModuleUrl) { $remoteCommandUrl = $pipeworksManifest.ModuleUrl } $moduleUrl = if ($request['Url'] -like "*.ashx*" -and $request['Url'] -notlike "*Default.ashx*") { $u = $request['Url'].ToString() $u = $u.Substring($u.LastIndexOf('/')) $remoteCommandUrl + $u } elseif ($request['Url'] -like "*.ashx*" -and$moduleUrl -like "*Default.ashx") { $remoteCommandUrl.Substring(0,$remoteCommandUrl.Length - "Default.ashx".Length - 1) } else { $remoteCommandUrl + "/" } $zipDownloadUrl = if ($allowDownload) { "<DirectDownload>$($moduleUrl.Substring(0,$moduleUrl.LastIndexOf("/")) + '/' + $module.Name + '.' + $module.Version + '.zip')</DirectDownload>" } else { "" } $facebookChunk = if ($pipeworksManifest.Facebook.AppId) { $scopeString = if ($pipeworksManifest.Facebook.Scope) { $pipeworksManifest.Facebook.Scope -join ", " } else { "email, user_birthday" } "<Facebook> <AppId>$($pipeworksManifest.Facebook.AppId)</AppId> <Scope>$scopeString</Scope> </Facebook>" } else { "" } $LogoChunk = if ($pipeworksManifest.Logo) { "<Logo>$([Security.SecurityElement]::Escape($pipeworksManifest.Logo))</Logo>" } else { "" } $pubCenterChunk =if ($pipeworksManifest.PubCenter) { $pubCenterId = if ($pipeworksmanifest.PubCenter.ApplicationId) { $pipeworksmanifest.PubCenter.ApplicationId } elseif ($pipeworksmanifest.PubCenter.Id) { $pipeworksmanifest.PubCenter.Id } if (-not $pubCenterId) { "" } else { " <PubCenter> <ApplicationID>$($pubCenterId)</ApplicationID> <TopAdUnit>$($pipeworksmanifest.PubCenter.TopAdUnit)</TopAdUnit> <BottomAdUnit>$($pipeworksmanifest.PubCenter.BottomAdUnit)</BottomAdUnit> </PubCenter>" } } else { "" } $adSenseChunk = if ($pipeworksManifest.AdSense) { $theAdSenseId = if ($pipeworksmanifest.AdSense.AdSenseId) { $pipeworksmanifest.AdSense.AdSenseId } elseif ($pipeworksmanifest.AdSense.Id) { $pipeworksmanifest.AdSense.Id } "<AdSense> <ApplicationID>$($TheAdSenseId)</ApplicationID> <TopSlot>$($pipeworksmanifest.AdSense.TopSlot)</TopSlot> <BottomSlot>$($pipeworksmanifest.AdSense.BottomAdSlot)</BottomSlot> </AdSense> " } else { "" } $commandTriggerChunk = if ($pipeworksmanifest.CommandTrigger) { $sortedTriggers = $pipeworksmanifest.CommandTrigger.GetEnumerator() | Sort-Object Key $commandTriggerXml = foreach ($trigger in $sortedTriggers) { " <CommandTrigger> <Trigger>$([Security.SecurityElement]::Escape($Trigger.Key))</Trigger> <Command>$([Security.SecurityElement]::Escape($Trigger.Value))</Command> </CommandTrigger>" } "<CommandTriggers> $($commandTriggerXml) </CommandTriggers>" } else { "" } $manifestXml = [xml]"<ModuleManifest> <Name>$($module.Name)</Name> <Url>$($moduleUrl)</Url> <Version>$($module.Version)</Version> <Description>$([Security.SecurityElement]::Escape($module.Description))</Description> $LogoChunk $styleChunk <Company>$($manifestObject.CompanyName)</Company> <Author>$($manifestObject.Author)</Author> <Copyright>$($manifestObject.Copyright)</Copyright> <Guid>$($manifestObject.Guid)</Guid> $zipDownloadUrl $facebookChunk $blogChunk $aboutChunk $topicGroupChunk $defaultCommmandChunk <AllCommands> $allCommandChunks </AllCommands> $webCommandsChunk $commandGroupChunk $commandTriggerChunk $pubCenterChunk $AdSenseChunk </ModuleManifest>" $application["ManifestXmlFor_$($module.Name)"] = $manifestXml; $strWrite = New-Object IO.StringWriter $manifestXml.Save($strWrite) $response.ContentType = 'text/xml' $response.Write("$strWrite") } $mailHandlers = if ($pipeworksManifest.Mail) { @" elseif (`$request['SendMail']) { $($mailHandler.ToString().Replace('"','""')) } "@ } else { "" } $checkoutHandlers = @" elseif (`$request['AddItemToCart']) { $($addCartHandler.ToString().Replace('"','""')) } elseif (`$request['ShowCart']) { $($ShowCartHandler.ToString().Replace('"','""')) } elseif (`$request['Checkout']) { $($checkoutCartHandler.ToString().Replace('"','""')) } "@ $TableHandlers = if ($pipeworksManifest.Table) { @" elseif (`$request['id']) { $($idHandler.ToString().Replace('"','""')) } elseif (`$request['object']) { $($objectHandler.ToString().Replace('"','""')) } elseif (`$request['Name']) { $($nameHandler.ToString().Replace('"','""')) } elseif (`$request['Latest']) { $($latestHandler.ToString().Replace('"','""')) } elseif (`$request['Rss']) { $($RssHandler.ToString().Replace('"','""')) } elseif (`$request['Type']) { $($typeHandler.ToString().Replace('"','""')) } elseif (`$request['Search']) { $($searchHandler.ToString().Replace('"','""')) } "@.TrimEnd() } else { "" } $userTableHandlers = if ($pipeworksManifest.UserTable) { @" elseif (`$request['Join']) { `$session['ProfileEditMode'] = `$true $($JoinHandler.ToString().Replace('"','""')) } elseif (`$request['EditProfile']) { `$editMode = `$true $($JoinHandler.ToString().Replace('"','""')) } elseif (`$request['ConfirmUser']) { $($ConfirmUserHandler.ToString().Replace('"','""')) } elseif (`$request['Login']) { $($LoginUserHandler.ToString().Replace('"','""')) } elseif (`$request['Logout']) { $($LogoutUserHandler.ToString().Replace('"','""')) } elseif (`$request['ShowApiKey']) { $($ShowApiKeyHandler.ToString().Replace('"','""')) } elseif (`$request['FacebookConfirmed']) { $($facebookConfirmUser.ToString().Replace('"','""')) } elseif (`$request['LiveIDConfirmed']) { $($liveIdConfirmUserHandler.ToString().Replace('"','""')) } elseif (`$request['FacebookLogin']) { $($facebookLoginDisplay.ToString().Replace('"','""')) } elseif (`$request['Purchase'] -or `$request['Rent']) { $($addPurchaseHandler.ToString().Replace('"','""')) } elseif (`$request['Settle']) { $($settleHandler.ToString().Replace('"','""')) } "@ } else { "" } #region GetExtraCommandInfo $getCommandExtraInfo = { param([string]$RequestedCommand) $command = if ($module.ExportedAliases[$RequestedCommand]) { $module.ExportedAliases[$RequestedCommand] } elseif ($module.ExportedFunctions[$requestedCommand]) { $module.ExportedFunctions[$RequestedCommand] } elseif ($module.ExportedCmdlets[$requestedCommand]) { $module.ExportedCmdlets[$RequestedCommand] } if ($command.ResolvedCommand) { $command = $command.Resolvedcommand } if (-not $command) { throw "$requestedCommand not found in module $module" } # Generate individual handlers $extraParams = if ($pipeworksManifest -and $pipeworksManifest.WebCommand.($Command.Name)) { $pipeworksManifest.WebCommand.($Command.Name) } elseif ($pipeworksManifest -and $pipeworksManifest.WebAlias.($Command.Name) -and $pipeworksManifest.WebCommand.($pipeworksManifest.WebAlias.($Command.Name).Command)) { $webAlias = $pipeworksManifest.WebAlias.($Command.Name) $paramBase = $pipeworksManifest.WebCommand.($pipeworksManifest.WebAlias.($Command.Name).Command) foreach ($kv in $webAlias.GetEnumerator()) { if (-not $kv) { continue } if ($kv.Key -eq 'Command') { continue } $paramBase[$kv.Key] = $kv.Value } $paramBase } else { @{ ShowHelp=$true } } if ($pipeworksManifest -and $pipeworksManifest.Style -and (-not $extraParams.Style)) { $extraParams.Style = $pipeworksManifest.Style } if ($extraParams.Count -gt 1) { # Very explicitly make sure it's there, and not explicitly false if (-not $extra.RunOnline -or $extraParams.Contains("RunOnline") -and $extaParams.RunOnline -ne $false) { $extraParams.RunOnline = $true } } if ($extaParams.PipeInto) { $extaParams.RunInSandbox = $true } if (-not $extraParams.AllowDownload) { $extraParams.AllowDownload = $allowDownload } if ($extraParams.RequireAppKey -or $extraParams.RequireLogin -or $extraParams.IfLoggedAs -or $extraParams.ValidUserPartition -or $extraParams.Cost -or $extraParams.CostFactor) { $extraParams.UserTable = $pipeworksManifest.Usertable.Name $extraParams.UserPartition = $pipeworksManifest.Usertable.Partition $extraParams.StorageAccountSetting = $pipeworksManifest.Usertable.StorageAccountSetting $extraParams.StorageKeySetting = $pipeworksManifest.Usertable.StorageKeySetting } if ($extraParams.AllowDownload) { # Downloadable Commands $downloadableCommands += $command.Name } if ($MarginPercentLeftString -and (-not $extraParams.MarginPercentLeft)) { $extraParams.MarginPercentLeft = $MarginPercentLeftString.TrimEnd("%") } if ($MarginPercentRightString-and -not $extraParams.MarginPercentRight) { $extraParams.MarginPercentRight = $MarginPercentRightString.TrimEnd("%") } } $getCommandTab = { param($cmd, [switch]$NavBarOnly) $CanCacheTab = $false # If the command is Marked Hidden, then it will not be displayed on a web interface. if ($pipeworksManifest.WebCommand.$cmd.Hidden) { return } $realCmd = $cmd if ($pipeworksManifest.WebAlias.$Cmd) { $realCmd = $pipeworksManifest.WebAlias.$cmd.Command } if (-not $realCmd) { return } if (-not $script:CachedResolvedCommand.($module.Name).$realCmd) { if (-not $script:CachedResolvedCommand) { $script:CachedResolvedCommand = @{} } if (-not $script:CachedResolvedCommand.($module.Name)) { $script:CachedResolvedCommand.($module.Name) = @{} } $script:CachedResolvedCommand.($module.Name).$realCmd = Get-Command $realcmd -ErrorAction SilentlyContinue } $resolvedCommand = $script:CachedResolvedCommand.($module.Name).$realCmd if (-not $resolvedCommand) { return } $cmdUrl = "${cmd}/?-widget" $hideParameter =@($pipeworksManifest.WebCommand.$realcmd.HideParameter) $cmdOptions = $pipeworksManifest.WebCommand.$realcmd if ($pipeworksManifest.WebAlias.$cmd) { foreach ($kv in ($pipeworksManifest.WebAlias.$cmd).GetEnumerator()) { if (-not $kv) { continue } if ($kv.Key -eq 'Command') { continue } $cmdOptions[$kv.Key] = $kv.Value } } $cmdFriendlyName = if ($pipeworksManifest.WebCommand.$realcmd.FriendlyName) { $pipeworksManifest.WebCommand.$realcmd.FriendlyName } else { $realCmd } $cmdIsVisible = $true if ($pipeworksManifest.WebCommand.$realcmd.IfLoggedInAs -or $pipeworksManifest.WebCommand.$realcmd.ValidUserPartition) { $confirmParams = @{ IfLoggedInAs = $pipeworksManifest.WebCommand.$realcmd.IfLoggedInAs ValidUserPartition = $pipeworksManifest.WebCommand.$realcmd.ValidUserPartition CheckId = $true WebsiteUrl = $finalUrl } $cmdIsVisible = . Confirm-Person @confirmParams } if ($cmdIsVisible) { $commandDescription = "" $command = $cmd if (-not $script:CachedCommandHelp.($module).$command) { if (-not $script:CachedCommandHelp) { $script:CachedCommandHelp = @{} } if (-not $script:CachedCommandHelp.($module)) { $script:CachedCommandHelp.($module) = @{} } $commandHelp = Get-Help $command -ErrorAction SilentlyContinue | Select-Object -First 1 $script:CachedCommandHelp.($module).$command = $commandHelp } else { $commandHelp = $script:CachedCommandHelp.($module).$command } if ($commandHelp.Description) { $commandDescription = $commandHelp.Description[0].text } if (-not $script:CachedCommandMarkdown.$command) { if (-not $script:CachedCommandMarkdown) { $script:CachedCommandMarkdown = @{} } $script:CachedCommandMarkdown.$command = ConvertFrom-markdown -markdown "$commandDescription " } $commandaction = if ($customAnyHandler) { "?Command=$realcmd" } else { "${RelativeDepth}$realcmd/" } " <div> $($script:CachedCommandMarkdown.$command) </div> <div id='${cmd}_container' style='padding:20px'> $( if ($cmdOptions.RequireLogin -and (-not $session['User'])) { $confirmHtml = . Confirm-Person -WebsiteUrl $finalUrl # Localize Content Here 'You have to log in' + $confirmHtml $CanCacheTab = $false return } if ($NavBarOnly) { return " " } if ($pipeworksManifest.Inline -eq '*' -or $pipeworksManifest.Inline -eq $realCmd) { $CanCacheTab = $false if ($cmdOptions.RunWithoutInput ) { $extraParams = @{} + $cmdOptions if ($pipeworksManifest -and $pipeworksManifest.Style -and (-not $extraParams.Style)) { $extraParams.Style = $pipeworksManifest.Style } if ($extraParams.Count -gt 1) { # Very explicitly make sure it's there, and not explicitly false if (-not $extra.RunOnline -or $extraParams.Contains("RunOnline") -and $extaParams.RunOnline -ne $false) { $extraParams.RunOnline = $true } } if ($extaParams.PipeInto) { $extaParams.RunInSandbox = $true } if (-not $extraParams.AllowDownload) { $extraParams.AllowDownload = $allowDownload } if ($extraParams.RunOnline) { # Commands that can be run online $webCmds += $command.Name } if ($extraParams.RequireAppKey -or $extraParams.RequireLogin -or $extraParams.IfLoggedAs -or $extraParams.ValidUserPartition) { $extraParams.UserTable = $pipeworksManifest.Usertable.Name $extraParams.UserPartition = $pipeworksManifest.Usertable.Partition $extraParams.StorageAccountSetting = $pipeworksManifest.Usertable.StorageAccountSetting $extraParams.StorageKeySetting = $pipeworksManifest.Usertable.StorageKeySetting } Invoke-Webcommand -Command $resolvedCommand @extraParams -AnalyticsId "$AnalyticsId" -AdSlot "$AdSlot" -AdSenseID "$AdSenseId" -ServiceUrl $finalUrl 2>&1 } else { $useAjax = if ($pipeworksManifest.NoAjax -or $cmdOptions.ContentType -or $cmdOptions.RedirectTo -or $cmdOptions.PlainOutput) { if ($request -and $request["Ajax"]) { $true } else { $false } } else { $true } Request-CommandInput -Action "$commandaction" -CommandMetaData (Get-Command $realcmd -Module "$($module.Name)") -DenyParameter $hideParameter -Ajax:$useAjax -ButtonText $cmdFriendlyName } } elseif ($pipeworksManifest.Link -eq '*' -or $pipeworksManifest.Link -eq $realCmd) { $cmdLinks["$cmdFriendlyName"] = $commandaction return " " } else { $cmdUrls["$cmdFriendlyName/?inline=true"] = $commandaction return " " } ) </div>" } } $getGroups = { param([switch]$NavBarOnly) $getGroupsStartedAt = [DateTime]::Now $CanCacheGroups = $true $realOrder = @() if ($pipeworksManifest.Group -or $pipeworksManifest.Groups) { $groups = New-Object Collections.ArrayList $groupInfo = if ($pipeworksManifest.Group) { $pipeworksManifest.Group } else { $pipeworksManifest.Groups } foreach ($grp in $groupInfo ) { if (-not $grp) { continue } if ($grp -isnot [hashtable]) { continue } $GroupIsVisible = $false $CanCacheGroup = $true foreach ($key in ($grp.Keys | Sort-Object)) { $innerLayers = @{} $innerRestUrls = @{} $innerLinks = @{} $navBarData[$key] = @{} $values = @($grp[$key]) $innerOrder = New-Object Collections.ArrayList $expandedLayers = @() foreach ($cmd in $values) { $top = $cmd $cmdFriendlyName = $cmd $t = "" $tab = if ($walkthrus[$top]) { $cmdFriendlyName = $top $namedtopics[$top] = $top # If it's inlined, get the walkthru HTML, otherwise, just get the link if ($pipeworksManifest.Inline -eq '*' -or $pipeworksManifest.Inline -eq $top) { if (-not $NavBarOnly) { $CanCacheGroup = $false $params = @{} if ($pipeworksManifest.TrustedWalkthrus -contains $top) { $params['RunDemo'] = $true } if ($pipeworksManifest.WebWalkthrus -contains $top) { $params['OutputAsHtml'] = $true } if (-not $script:CachedWalkthrus) { $script:CachedWalkthrus = @{} } if (-not $script:CachedWalkthrus["${module}_${top}"]) { $script:CachedWalkthrus["${module}_${top}"] = Write-WalkthruHTML -StepByStep -WalkthruName $top -WalkThru $walkthrus[$top] @params } $script:CachedWalkthrus["${module}_${top}"] if ($pipeworksManifest.Inline -ne '*') { $expandedLayers += $top } } else { " " } } elseif ($pipeworksManifest.Link -eq '*' -or $pipeworksManifest.Link -eq $top) { " " $innerLinks[$cmdFriendlyName] = "${RelativeDepth}${cmd}" } else { " " $innerRestUrls[$cmdFriendlyName] = "${RelativeDepth}${cmd}/?inline=true" } } elseif ($aboutTopicsByName[$top]) { $cmdFriendlyName= $top $namedtopics[$top] = $top $topicMatch = $aboutTopicsByName[$top] if ($pipeworksManifest.Inline -eq '*' -or $pipeworksManifest.Inline -eq $top) { if (-not $NavBarOnly) { $CanCacheGroup = $false if (-not $script:CachedTopics) { $script:CachedTopics = @{} } if (-not $script:CachedTopics["${module}_${top}"]) { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } $script:CachedTopics["${module}_${top}"] = ConvertFrom-Markdown -Markdown "$($topicMatch.Topic) " -ScriptAsPowerShell -ShowData:$ShowDataInTopic } $script:CachedTopics["${module}_${top}"] if ($pipeworksManifest.Inline -ne '*') { $expandedLayers += $top } } else { " " } } elseif ($pipeworksManifest.Link -eq '*' -or $pipeworksManifest.Link -eq $top) { " " $innerLinks[$cmdFriendlyName] = "${RelativeDepth}${cmd}" } else { " " $innerRestUrls[$cmdFriendlyName] = "${RelativeDepth}${cmd}/?snug=true" } } else { if ($pipeworksManifest.Inline -eq '*' -or $pipeworksManifest.Inline -eq $cmd) { $CanCacheGroup = $false . $getCommandTab $cmd -navbarOnly:$NavBarOnly if ($cmdFriendlyName -and $pipeworksManifest.Inline -ne '*') { $expandedLayers += $cmdFriendlyName } } elseif ($pipeworksManifest.Link -eq '*' -or $pipeworksManifest.Link -eq $cmd) { . $getCommandTab $cmd -navbarOnly if (-not $cancacheTab) { $CanCacheGroup = $false } $innerLinks[$cmdFriendlyName] = "${RelativeDepth}${cmd}" } else { . $getCommandTab $cmd -navbarOnly if (-not $cancacheTab) { $CanCacheGroup = $false } $innerRestUrls[$cmdFriendlyName] = "${RelativeDepth}${cmd}/?inline=true" } } if ($tab) { $innerLayers[$cmdFriendlyName] = $tab $null = $innerOrder.Add($cmdFriendlyName) $navBarData[$key][$cmdFriendlyName] = "${relativeDepth}${cmd}".Replace(" ", "_").TrimEnd("/") + "/" $GroupIsVisible = $true } } $regionLayoutParams = if ($pipeworksManifest.InnerRegion -is [Hashtable]) { $pipeworksManifest.InnerRegion } else { @{ AsNewspaper = $true ExpandNewspaperColumn = $expandedLayers NewspaperColumn = 1 NewspaperHeadingSize = 3 } } if ($GroupIsVisible) { $cmdTabs[$key] = New-Region @regionLayoutParams -LayerID $Key -Layer $innerLayers -Order $innerOrder -LayerUrl $innerRestUrls -LayerLink $innerLinks $navBarOrder[$key] = $innerOrder $null = $groups.Add($key) } if (-not $CanCacheGroup) { $CanCacheGroups = $false } } } $realOrder += $groups # Filter out anything displayed elsewhere $screencasts = @( foreach ($_ in $screencasts) { if ($_.Caption -and -not $namedtopics[$_.Caption]) { $_ } }) $onlineWalkthrus = @( foreach ($_ in $onlineWalkthrus) { if ($_.Caption -and -not $namedtopics[$_.Caption]) { $_ } }) $codeWalkThrus = @( foreach ($_ in $codeWalkThrus) { if ($_.Caption -and -not $namedtopics[$_.Caption]) { $_ } }) $tutorialItems = @( foreach ($_ in $tutorialItems) { if ($_.Caption -and -not $namedtopics[$_.Caption]) { $_ } }) } else { $commandOrder = if ($pipeworksManifest.CommandOrder) { $pipeworksManifest.CommandOrder } else { $pipeworksManifest.WebCommand.Keys | Sort-Object } $expandedLayers = @() foreach ($cmd in $commandOrder) { $tab = . $getCommandTab $cmd if (-not $cancacheTab) { $CanCacheGroups = $false } if ($tab) { . ([ScriptBlock]::create(" `${Global:$($cmdFriendlyName)} = `$tab `${Global:$($cmd)} = `$tab ")) $cmdTabs[$cmdFriendlyName] = $tab if ($pipeworksManifest.Inline -eq '*' -or $pipeworksManifest.Inline -eq $cmd) { if ($pipeworksManifest.Inline -ne '*') { $expandedLayers += $cmdFriendlyName } } elseif ($pipeworksManifest.Link -eq '*' -or $pipeworksaManifest.Link -eq $cmd) { $cmdLinks[$cmdFriendlyName] = "${RelativeDepth}${cmd}/" } else { $cmdUrls[$cmdFriendlyName] = "${RelativeDepth}${cmd}/?inline=true" } $realOrder += $cmdFriendlyName $navBarData[$cmdFriendlyName] = "${RelativeDepth}${cmd}".Replace(" ", "_").TrimEnd("/") + "/" $navBarUrls[$cmdFriendlyName] = "${RelativeDepth}${cmd}".Replace(" ", "_").TrimEnd("/") + "/" } } } $timespentIngetGroups = [DateTime]::Now - $getGroupsStartedAt } #endregion $getBanners = { $bottomBannerSlot = if ($pipeworksManifest.AdSense -and $PipeworksManifest.AdSense.BottomAdSlot) { if ($PipeworksManifest.AdSense.BottomAdSlot -like "*/*") { $slotAdSenseId = $pipeworksManifest.AdSense.BottomAdSlot.Split("/")[0] $slotAdSlot = $pipeworksManifest.AdSense.BottomAdSlot.Split("/")[1] } elseif ($pipeworksManifest.AdSense.Id) { $slotAdSenseId = $pipeworksManifest.AdSense.Id $slotAdSlot = $PipeworksManifest.AdSense.BottomAdSlot } "<p style='text-align:center'> <script type='text/javascript'> <!-- google_ad_client = 'ca-pub-$($slotAdSenseId)'; /* AdSense Banner */ google_ad_slot = '$($slotAdSlot)'; google_ad_width = 728; google_ad_height = 90; //--> </script> <script type='text/javascript' src='http://pagead2.googlesyndication.com/pagead/show_ads.js'> </script> </p>" } else { "" } $upperBannerSlot = if ($pipeworksManifest.AdSense -and $PipeworksManifest.AdSense.TopAdSlot) { if ($PipeworksManifest.AdSense.TopAdSlot -like "*/*") { $slotAdSenseId = $pipeworksManifest.AdSense.TopAdSlot.Split("/")[0] $slotAdSlot = $pipeworksManifest.AdSense.TopAdSlot.Split("/")[1] } elseif ($pipeworksManifest.AdSense.Id) { $slotAdSenseId = $pipeworksManifest.AdSense.Id $slotAdSlot = $PipeworksManifest.AdSense.TopAdSlot } "<p style='text-align:center'> <script type='text/javascript'> <!-- google_ad_client = 'ca-pub-$($slotAdSenseId)'; /* AdSense Banner */ google_ad_slot = '$($slotAdSlot)'; google_ad_width = 728; google_ad_height = 90; //--> </script> <script type='text/javascript' src='http://pagead2.googlesyndication.com/pagead/show_ads.js'> </script> </p>" } else { "" } $brandingSlot = if ($script:CachedBrandingSlot) { $script:CachedBrandingSlot } else { if ($pipeworksManifest.Branding) { if ($pipeworksManifest.Branding) { $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } ConvertFrom-Markdown $pipeworksManifest.Branding -ShowData:$ShowDataInTopic } else { "" } } elseif ($ModuleMaker -eq 'Start-Automating') { @" <div style='font-size:.75em;text-align:right'> Provided By <a href='http://start-automating.com'> <img src='http://StartAutomating.com/Assets/StartAutomating_100_Transparent.png' align='middle' style='width:60px;height:60px;border:0' /> </a> </div> <div style='font-size:.75em;text-align:right'> Powered With <a href='http://powershellpipeworks.com'> <img src='http://powershellpipeworks.com/assets/powershellpipeworks_150.png' align='middle' style='width:60px;height:60px;border:0' /> </a> </div> "@ } else { @" <div style='font-size:.75em;text-align:right'> Powered With <a href='http://powershellpipeworks.com'> <img src='http://powershellpipeworks.com/assets/powershellpipeworks_150.png' align='middle' style='width:60px;height:60px;border:0' /> </a> </div> "@ } } $script:CachedBrandingSlot = $brandingSlot } $coreModuleHandler = { $coreHandlerStartedAt = [DateTime]::Now # Here is where the default module experience happens. <# # This consists of declaring several variables that can be used within templates $TitleArea - An area containing the title of the module the module logo $descriptionArea - An area containing the description of the module or the current command $socialArea An area containing social media, login links, and company contact info #> <# $TitleArea - An area containing the title of the module the module logo #> $linkUrl = if ("$finalUrl") { "$FinalUrl".Substring(0, "$FinalUrl".LastIndexOf("/")) } else { "./" } $titleArea = if ($PipeworksManifest -and $pipeworksManifest.Logo) { "<a href='$linkUrl' class='brand'><img src='$($pipeworksManifest.Logo)' alt='$($module.Name)' style='border:0' /></a>" } else { "<a href='$linkUrl' class='brand'>$($Module.Name)</a>" } $socialArea = '' $descriptionArea = $module.Description.Replace("`n", "<br/>") $cmdTabs = @{} $navBarData = @{} $navBarUrls = @{} $navBarOrder = @{} if ($AllowDownload) { if ($pipeworksManifest.Technet.Url -or $pipeworksManifest.Win8.PublishedUrl -or $PipeworksManifest.Github.Url) { $downloads = @{ "Download Latest" = "Download.html" } if ($PipeworksManifest.Technet.Url) { $downloads+= @{ "Download From Technet" = "$($pipeworksManifest.Technet.Url)" } } if ($PipeworksManifest.Win8.PublishedUrl) { $downloads+= @{ "Download Windows App" = "$($PipeworksManifest.Win8.PublishedUrl)" } } if ($PipeworksManifest.Github.Url) { $downloads+= @{ "Download From Github" = "$($PipeworksManifest.Github.Url)" } } $navBarData["Download"] = $downloads } else { $navBarData["Download"] = "" $navBarUrls["Download"] = "Download.html" } } if ($pipeworksManifest.Win.PublishedUrl -and -not $allowDownload) { } $cmdUrls = @{} $cmdLinks = @{} $telephoneArea = "" $addressArea = "" $emailArea = "" $orgArea = "" $orgItems = @() $OrgInfoSlot = if ($pipeworksManifest.Organization) { if ($pipeworksManifest.Organization.Telephone) { $telephoneArea = ($pipeworksManifest.Organization.Telephone -join ' | ') + "<BR/>" $orgItems += $telephoneArea } if ($pipeworksManifest.Organization.Address) { $addressArea = $pipeworksManifest.Organization.Address -split ([Environment]::NewLine) -join '<br/>' $orgItems += $addressArea } if ($pipeworksManifest.Organization.Email) { $emailArea = ( "<a href='mailto:$($pipeworksManifest.Organization.Email)'>$($pipeworksManifest.Organization.Email)</a>" + "<BR/>" ) $orgItems += $emailArea } $orgArea = $orgItems -ne '' -join '<br/>' # $socialArea += $orgText } else { "" } $loginRequired = $false if ($PipeworksManifest.WebCommand) { foreach ($_ in $PipeworksManifest.WebCommand.Values) { if ($_.RequireLogin -or $_.RequireAppKey -or $_.IfLoggedInAs -or $_.ValidUserPartition) { $loginRequired = $true break } } } if (-not $script:SocialArea) { $script:SocialArea = " $( if (-not $antiSocial) { if ($pipeworksManifest -and ($pipeworksManifest.Facebook.AppId -or $pipeworksManifest.FacebookLike)) { Write-Link "facebook:like" } if ($pipeworksManifest -and ($pipeworksManifest.GoogleSiteVerification -or $pipeworksManifest.AddPlusOne)) { Write-Link "google:plusone" } if ($pipeworksManifest -and $pipeworksManifest.ShowTweet) { Write-Link "twitter:tweet" } elseif ($pipeworksManifest -and ($pipeworksManifest.TwitterId)) { Write-Link "twitter:tweet" Write-Link "twitter:follow@$($pipeworksManifest.TwitterId.TrimStart('@'))" } } ) " } $socialArea = $script:SocialArea $confirmationArea = "" if ($loginRequired) { $confirmationArea = . Confirm-Person -WebsiteUrl $finalUrl } $topicHtml = "" $subtopics = @{ LayerId = 'MoreInfo' Layer = @{} } $webPageRss = @{} $ShowBuiltInBlog = $true if ($PipeworksManifest.Blog -and $PipeworksManifest.Blog.Name) { $blogLink = if ($pipeworksManifest.Blog.Link -like "http*" -and $pipeworksManifest.Blog.Name -and $pipeworksManifest.Blog.Description) { # Absolute link to module base, $pipeworksManifest.Blog.Link.TrimEnd("/") + "/Module.ashx?rss=$($pipeworksManifest.Blog.Name)" } elseif ($pipeworksManifest.Blog.Link) { $pipeworksManifest.Blog.Link } else { "Module.ashx?Rss=true" $ShowBuiltInBlog = $false } $webPageRss += @{ $PipeworksManifest.Blog.Name=$blogLink } } $topicsByName = @{} if ($aboutTopics) { $coreAboutTopic = $null $otherAboutTopics = @(foreach ($_ in $aboutTopics) { if (-not $_) {continue } if ($_.Name -ne "About $($Module.Name)") { $_ } else { $coreAboutTopic = $_ } }) if ($coreAboutTopic) { $coreAboutTopic = @($coreAboutTopic)[0] $ShowDataInTopic = if ($pipeworksManifest.HideDataInTopic) { $false } else { $true } if (-not $script:CachedCoreTopic.($module.Name)) { if (-not $script:CachedCoreTopic) { $script:CachedCoreTopic = @{} } $script:CachedCoreTopic.($module.Name) = ConvertFrom-Markdown -Markdown "$($coreAboutTopic.Topic) " -ScriptAsPowerShell -ShowData:$ShowDataInTopic } $topicHtml = $script:CachedCoreTopic.($module.Name) # ConvertFrom-Markdown -Markdown "$($coreAboutTopic.Topic) " -ScriptAsPowerShell -ShowData:$ShowDataInTopic } if ($otherAboutTopics -and -not ($pipeworksManifest.HideUngrouped -or $pipeworksManifest.HideUngroupedTopics)) { $aboutItems = @() $tutorialItems = @() foreach ($oat in $otherAboutTopics) { $tutorialItems += if ($customAnyHandler) { New-Object PSObject -Property @{ Caption = $oat.Name Url = "?About=" + $oat.Name } } else { New-Object PSObject -Property @{ Caption = $oat.Name Url = $oat.Name + "/" } } } if ($ShowBuiltInBlog) { $webPageRss["$($module)"] = "${RelativeDepth}Module.ashx?rss=true" } if ($tutorialLayer.Count) { } if ($aboutLayer.Count) { } } } if ($walkthrus -and -not ($pipeworksManifest.HideUngrouped -or $pipeworksManifest.HideUngroupedTopics)) { $screenCasts = @() $onlineWalkthrus = @() $codeWalkThrus = @() foreach ($walkthruName in $walkThrus.Keys) { if ($walkThruName -like "*Video*" -or $walkThruName -like "*Screencasts*") { $screenCasts += if ($customAnyHandler) { New-Object PSObject -Property @{ Caption = $walkThruName.Replace('.walkthru.help.txt', '').Replace('_', ' ') Url = "?Walkthru=" + $walkThruName.Replace('.walkthru.help.txt', '').Replace('_', ' ') } } else { New-Object PSObject -Property @{ Caption = $walkThruName.Replace('.walkthru.help.txt', '').Replace('_', ' ') Url = $walkThruName.Replace('.walkthru.help.txt', '').Replace('_', ' ') + "/" } } } elseif ($pipeworksManifest.TrustedWalkthrus -contains $walkThruName) { $onlineWalkThrus += if ($customAnyHandler) { New-Object PSObject -Property @{ Caption = $walkThruName Url = "?Walkthru=" + $walkThruName } } else { New-Object PSObject -Property @{ Caption = $walkThruName Url = $walkThruName + "/" } } } else { $codeWalkThrus += if ($customAnyHandler) { New-Object PSObject -Property @{ Caption = $walkThruName Url = "?Walkthru=" + $walkThruName } } else { New-Object PSObject -Property @{ Caption = $walkThruName Url = $walkThruName + "/" } } } } } if ($request -and $request["Snug"]) { $MarginPercentLeftString = $MarginPercentRightString = "1%" } #region Services Tab $StartedGeneratingCoreContent = [DateTime]::Now if (-not $canCacheGroups) { . $getGroups } $videosOrder = @() $screenCastSection = if ($screenCasts -and -not ($pipeworksManifest.HideUngrouped -or $pipeworksManifest.HideUngroupedTopics)) { $navBarData["Videos"] = @{} $subTopics.Layer."Videos" = @" <p class='ModuleWalkthruExplanation'> Watch these videos to get started: $($ScreenCasts | Sort-Object Caption | ForEach-Object { $navBarData["Videos"][$($_.Caption)] = "$RelativeDepth$($_.Url)".Replace(' ', '%') $videosOrder += $_.Caption $_ } | Write-Link -AsList) </p> "@ $navBarOrder["Videos"] = $videosOrder } else { "" } $demoOrder = @() $onlineWalkthruSection = if ($onlineWalkthrus -and -not ($pipeworksManifest.HideUngrouped -or $pipeworksManifest.HideUngroupedTopics)) { $navBarData["Demos"] = @{} $subTopics.Layer."Demos" = @" <p class='ModuleWalkthruExplanation'> See each step, and see each step's results. $($OnlineWalkthrus| Sort-Object Caption| ForEach-Object { $navBarData["Demos"][$_.Caption] = "$RelativeDepth$($_.Url)".Replace(' ', '%') $demoOrder += $_.Caption $_ } | Write-Link -AsList ) </p> "@ $navBarOrder["Demos"] = $demoOrder } else { "" } $walkthruOrder = @() $codeWalkthruSection = if ($codeWalkThrus -and -not ($pipeworksManifest.HideUngrouped -or $pipeworksManifest.HideUngroupedTopics)) { $navBarData["Walkthrus"] = @{} $subTopics.Layer."Walkthrus" = @" <p class='ModuleWalkthruExplanation'> See the code step by step. $($CodeWalkthrus | Sort-Object Caption| ForEach-Object { $navBarData["Walkthrus"][$_.Caption] = "$RelativeDepth$($_.Url)".Replace(' ', '%') $walkthruOrder += $_.Caption $_ } | Write-Link -AsList) </p> "@ $navBarOrder["Walkthrus"] = $walkthruOrder } else { "" } if ($aboutItems -or $tutorialItems -and -not ($pipeworksManifest.HideUngrouped -or $pipeworksManifest.HideUngroupedTopics)) { $MoreAboutModule = @() $navBarData["More About $Module"] = @{} $subTopics.Layer."More About $Module" = @" <p class='ModuleWalkthruExplanation'> $($aboutItems + $tutorialItems | Sort-Object Caption | ForEach-Object { $navBarData["More About $Module"]["$($_.Caption)"] = "$RelativeDepth$($_.Url)".Replace(' ', '%') $MoreAboutModule += $_.Caption $_ } | Write-Link -AsList) </p> "@ $navBarOrder["More About $Module"] =$MoreAboutModule } $learnMore = if ($subtopics.Layer.Count) { foreach ($layerName in "More About $module", "Videos", "Walkthrus", "Demos") { if( $subtopics.Layer.$layerName) { $realOrder += $layerName if (-not $cmdTabs) { $cmdTabs = @{} } $cmdTabs[$layerName] = $subtopics.Layer.$layerName } } "" } else { "" } if ($AllowDownload) { if ($pipeworksManifest.Technet.Url -or $PipeworksManifest.Win8.PublishedURL -or $PipeworksManifest.Github.Url) { $downloads = @{ "Download Latest" = "Download.html" } $downloadLayers = @{ "Download Latest" = " " } if ($PipeworksManifest.Technet.Url) { $downloads += @{ "Download From Technet" = $PipeworksManifest.Technet.Url } $downloadLayers += @{ "Download From Technet" = " " } } if ($PipeworksManifest.Win8.PublishedURL) { $downloads +=@{ "Download Windows App" = $PipeworksManifest.Win8.PublishedURL } $downloadLayers += @{ "Download Windows App" = " " } } if ($PipeworksManifest.Github.Url) { $downloads +=@{ "Download From GitHub" = $PipeworksManifest.Github.Url } $downloadLayers += @{ "Download From GitHub" = " " } } $layerName = "Download" $regionLayoutParams = if ($pipeworksManifest.InnerRegion -is [Hashtable]) { $pipeworksManifest.InnerRegion } else { @{ AsNewspaper = $true ExpandNewspaperColumn = "" NewspaperColumn = 1 NewspaperHeadingSize = 3 NewspaperHeadingAlignment = 'center' } } $cmdTabs[$layerName] = New-Region -LayerID 'DownloadLayer' -layerLink $downloads -Layer $downloadLayers @regionLayoutParams $realOrder += "Download" } else { $layerName = "Download" $realOrder += "Download" $cmdTabs[$layerName] = " " $cmdLinks += @{Download="Download.html"} } } if ($pipeworksManifest.Win8.PublishedUrl) { if (-not $allowDownload) { $cmdLinks += @{"Download Windows App"=$pipeworksManifest.Win8.PublishedUrl} $layerName = "Download Windows App" $realOrder += "Download Windows App" $cmdTabs[$layerName] = " " } } $regionLayoutParams = if ($pipeworksManifest.MainRegion -is [hashtable]) { $pipeworksManifest.MainRegion } else { if ($pipeworksManifest.Group) { @{ AsNewspaper=$true #UnderlineNewspaperHeadline = $true UseButtonForNewspaperHeadline = $true } } else { @{ AsNewspaper=$true NewspaperColumn = 1 ExpandNewspaperColumn = $expandedLayers UseButtonForNewspaperHeadline = $true NewspaperHeadingSize = 3 NewspaperHeadingAlignment = "center" } } } $rest = if ($canCacheGroups) { if (-not $script:CachedGroupHTML.($module.Name)) { if (-not $script:CachedGroupHTML) { $script:CachedGroupHTML = @{} } $script:CachedGroupHTML.($module.Name)= New-Region -LayerID Items -Layer $cmdTabs -order $realOrder @regionLayoutParams -LayerUrl $cmdUrls -layerLink $cmdLinks $script:CachedGroupHTML.($module.Name) } else { $script:CachedGroupHTML.($module.Name) } } else { New-Region -LayerID Items -Layer $cmdTabs -order $realOrder @regionLayoutParams -LayerUrl $cmdUrls -layerLink $cmdLinks } $TimeSpentGeneratingCoreContent = [DateTime]::now - $StartedGeneratingCoreContent #endregion Services Tab . $getBanners $ifTemplateFound = @{} $rssLink = if ($webPageRss.Count -ge 1 -and $otherAboutTopics.Count -and $pipeworksManifest.Blog) { $rsslinks = foreach ($rss in $webPageRss.GetEnumerator()) { if (-not $rss) { continue } "<a href='$($rss.Value)'><img src='/rss.png' style='border:0;' alt='$([Web.HttpUtility]::HtmlAttributeEncode($rss.Key))' /></a>" } $rsslinks -join ("<br/>") } else { " " } if ($pipeworksManifest.TemplateFiles.($module.Name)) { $ifTemplateFound.Template =($module.Name) } elseif ($pipeworksManifest.ModuleTemplate) { $ifTemplateFound.Template =$pipeworksManifest.ModuleTemplate } elseif ($pipeworksManifest.DefaultTemplate) { $ifTemplateFound.Template =$pipeworksManifest.DefaultTemplate } elseif ($pipeworksManifest.Template) { $ifTemplateFound.Template =$pipeworksManifest.Template } if (-not $ModuleTemplateExists) { $ModuleTemplateExists = "Template", "Templates" | Get-ChildItem -ErrorAction SilentlyContinue -Filter "$($ifTemplateFound.Template).pswt" | Select-Object -First 1 # If the template won't make use of automatically generated sections, then don't generate them. $makeNavBar = $true $makeSlideShow = $true $showDefaultCommand = $true if ($global:ModuleTemplateExists) { $templateText = [IO.File]::ReadAllText($global:ModuleTemplateExists.FullName) $makeNavBar = [Regex]::Match($templateText, '\$navBarHtml') $makeSlideShow = [Regex]::Match($templateText, '\$slideShowHtml') $showDefaultCommand = [Regex]::Match($templateText, '\$defaultCommandSection') } } if ($makeNavBar) { $navBarHtml = if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { "" } } if ($makeSlideShow) { $slideShowHtml = "" if ($pipeworksManifest.Slideshow) { $slidesInShow = @{} $slideShowNaturalOrder = @() $slideCount = 0 $slideList = if ($pipeworksManifest.SlideShow.Slide) { $pipeworksManifest.SlideShow.Slide } elseif ($pipeworksManifest.SlideShow.Slides){ $pipeworksManifest.SlideShow.Slides } else { $null } foreach ($slide in $slideList) { if (-not $slide) { continue } if ($slide -is [Hashtable]) { foreach ($k in ($slide.Keys | Sort-Object)) { $slideName = $k $slideShowNaturalOrder += $k $slidesInShow[$k] = $slide[$k] } } elseif ($slide -as [string]) { $slideName = "Slide" + $slideCount $slideCount++ $slidesInShow[$slideName] = "<img src='$($slide)' style='border:0;max-width:75%' />" $slideShowNaturalOrder += $slideName } } if ($pipeworksManifest.Slideshow.Order) { $slideShowOrder = $pipeworksManifest.Slideshow.Order } else { $slideShowOrder = $slideShowNaturalOrder } $slideShowParams = @{} if ($PipeworksManifest.UseBootstrap) { $slideShowParams["AsCarousel"] = $true $slideShowParams["HideSlideNameButton"] = $true } else { $slideShowParams["AsSlideShow"] = $true $slideShowParams["UseDotInsteadOfName"] = $true } $slideShowHtml = New-Region -LayerID MainSlideshow -Layer $slidesInShow -Order $slideShowOrder @slideShowParams } } if ($showDefaultCommand) { $defaultCommandSection = if ($pipeworksManifest.DefaultCommand) { $defaultCmd = @($ExecutionContext.InvokeCommand.GetCommand($pipeworksManifest.DefaultCommand.Name, "All"))[0] $defaultCmdParameter = if ($pipeworksManifest.DefaultCommand.Parameter) { $pipeworksManifest.DefaultCommand.Parameter } else { @{} } $cmdOutput = & $defaultcmd @defaultCmdParameter if ($pipeworksManifest.DefaultCommand.GroupBy) { $defaulItem ="" $CmdOutputGrouped = $cmdOutput | Group-Object $pipeworksManifest.DefaultCommand.GroupBy | Foreach-Object -Begin { $groupedLayers = @{} $asStyle = if ($pipeworksManifest.DefaultCommand.DisplayAs) { "As$($pipeworksManifest.DefaultCommand.DisplayAs)" } else { "AsSlideshow" } } { if (-not $defaulItem ) { $defaultItem = $_.Name } $groupedLayers[$_.Name] = $_.Group | Out-HTML } -End { $asStyleParam = @{ $AsStyle = $true } New-Region -Default $defaultItem -Layer $groupedLayers -LayerID DefaultcommandSection @asStyleParam } $cmdOutputGrouped } else { $cmdOutput | Out-Html } } else { "" } } $pageHtml = if (-not $ifTemplateFound.Template) { " <div style='float:right;position:absolute;zindex:30;right:15px;top:15px;'> $socialArea </div> <div style='float:left'> <h1 style='float:left'>$titleArea</h1> <h2 style='text-align:right;float:left;margin-top:75px'> $descriptionArea </h2> $(if ($navBarData -and $PipeworksManifest.UseBootstrap) { New-Region -LayerID "MainNavBar" -Layer $navBarData -AsNavbar -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -LayerInnerOrder $navBarOrder } elseif ($navBarData) { New-Region -LayerId "MainMenu" -AsMenu -Style @{"float"="right"} -Order $realOrder -LayerUrl $navBarUrls -Layer $navBarData -LayerInnerOrder $navBarOrder } else { '' }) </div> " + ($spacingDiv * 3) + "<div style='margin-top:1%'>$topicHtml</div>" + "<div style='clear:both;margin-top:1%'>$upperBannerSlot</div>" + "<div style='clear:both;margin-top:1%'>$defaultCommandSection</div>" + $rest + "<div style='clear:both;margin-top:1%'>$bottomBannerSlot</div>" + "<div style='float:right;margin-top:15%'>$brandingSlot</div>" | New-Region -Style @{ "Margin-Left" = $MarginPercentLeftString "Margin-Right" = $MarginPercentRightString } } else { " " } if (-not $BakingPage) { $pageHtml | New-WebPage -NoCache -Title $module.Name -Description $module.Description -Rss $webPageRss @ifTemplateFound | Out-HTML -WriteResponse } else { $pageHtml | New-WebPage -NoCache -Title $module.Name -Description $module.Description -Rss $webPageRss @ifTemplateFound } $timeSpentInCoreHandler = [DateTime]::Now - $coreHandlerStartedAt } $antiSocial = if ($pipeworksManifest.AntiSocial) { $true } else { $false } $getVisibleGroups = { if (-not ($session -and $session["User"]) -and $request["LiveIdAccessToken"]) { $accessToken = $request["LiveIdaccesstoken"] . Confirm-Person -liveIDAccessToken $accessToken -WebsiteUrl $finalUrl } #if (-not ($session -and $session["User"])) { return } if ($pipeworksManifest.Group) { $groupVisibilityXml = "<VisibleGroups>" foreach ($g in $pipeworksManifest.Group) { foreach ($i in $g.GetEnumerator()) { $groupIsVisible = $false $groupXml = "<Group Name='$([security.securityElement]::Escape($i.Key))'>" foreach ($v in $i.Value) { if ($pipeworksManifest.WebCommand.$v) { if ($pipeworksManifest.WebCommand.$v.IfLoggedInAs -or $pipeworksManifestPath.WebCommand.$v.ValidUserPartition) { $ok = Confirm-person -websiteUrl $finalUrl -IfLoggedInAs $pipeworksManifest.WebCommand.IfLoggedInAs -ValidUserPartition $pipeworksManifest.WebCommand.ValidUserPartition -CheckId if ($ok) { $groupIsVisible = $true $groupXml += "<Item>$($v)</Item>" } } else { $groupIsVisible = $true $groupXml += "<Item>$($v)</Item>" } # It's a command } else { # It's a topic $groupXml += "<Item>$($v)</Item>" $groupIsVisible = $true } } if ($groupIsVisible) { $groupXml += "</Group>" $groupVisibilityXml += $groupXml } } } } if ($groupVisibilityXml) { if ($session -and $session["User"]) { $groupVisibilityXml+="<LoggedIn/>" } else { } $groupVisibilityXml += "</VisibleGroups>" $response.ContentType = "text/xml" $strWrite = New-Object IO.StringWriter ([xml]($groupVisibilityXml)).Save($strWrite) $resultToOutput = "$strWrite" -replace "encoding=`"utf-16`"", "encoding=`"utf-8`"" $response.Write("$ResultToOutput") } return } <#$moduleInit = @" $embedCommand $getModuleMetaData `$getCommandExtraInfo = { $($getCommandExtraInfo.ToString()) } `$getCommandTab = { $($getCommandTab.ToString()) } `$getGroups = { $($getGroups.ToString()) } `$getBanners ={ $($getBanners.ToString()) } `$cssStyle = $((Write-PowerShellHashtable $Style)) `$HalfMarginPercentLeftString = '$(($MarginPercentLeftString.Replace('%', '') -as [double])/2)%' `$HalfMarginPercentRightString = '$(($MarginPercentRightString.Replace('%', '') -as [double])/2)%' `$MarginPercentLeftString = '$MarginPercentLeftString'.Trim() `$MarginPercentRightString = '$MarginPercentRightString'.Trim() `$DownloadUrl = '$DownloadUrl' `$analyticsId = '$analyticsId' `$allowDownload = $(if ($allowDownload) { '$true'} else {'$false'}) `$antiSocial= $(if ($antiSocial) { '$true'} else {'$false'}) `$highlightedModuleCommands = '$($CommandOrder -join "','")' $($resolveFinalUrl.ToString()) "@#> $moduleHandler = @" WebCommandSequence.InvokeScript(@" if (-not `$global:ExecutionPolicyChanged) { Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force `$global:ExecutionPolicyChanged = `$true `$getCommandExtraInfo = { $($getCommandExtraInfo.ToString().Replace('"', '""')) } `$getCommandTab = { $($getCommandTab.ToString().Replace('"', '""')) } `$getGroups = { $($getGroups.ToString().Replace('"', '""')) } `$getBanners ={ $($getBanners.ToString().Replace('"', '""')) } `$cssStyle = $((Write-PowerShellHashtable $Style).Replace('"','""')) `$HalfMarginPercentLeftString = '$(($MarginPercentLeftString.Replace('%', '') -as [double])/2)%' `$HalfMarginPercentRightString = '$(($MarginPercentRightString.Replace('%', '') -as [double])/2)%' `$MarginPercentLeftString = '$MarginPercentLeftString'.Trim() `$MarginPercentRightString = '$MarginPercentRightString'.Trim() `$DownloadUrl = '$DownloadUrl' `$analyticsId = '$analyticsId' `$allowDownload = $(if ($allowDownload) { '$true'} else {'$false'}) `$antiSocial= $(if ($antiSocial) { '$true'} else {'$false'}) `$highlightedModuleCommands = '$($CommandOrder -join "','")' } $($embedCommand.ToString().Replace('"','""')) $($getModuleMetaData.ToString().Replace('"', '""')) $($resolveFinalUrl.ToString().Replace('"', '""')) `if (`$request['about']) { $($aboutHandler.ToString().Replace('"','""')) } elseif (`$request['VisibleGroup']) { $($getVisibleGroups.ToString().Replace('"','""')) } elseif (`$request['ShowPrivacyPolicy']) { $($privacyPolicyHandler.ToString().Replace('"','""').Replace('THE COMPANY', $module.CompanyName)) } elseif (`$request['walkthru']){ $($walkthruHandler.ToString().Replace('"','""')) } elseif (`$request.QueryString.ToString() -ieq '-TopicRSS' -or `$request['TopicRSS']) { $($topicRssHandler.ToString().Replace('"','""')) } elseif (`$request.QueryString.ToString() -ieq '-WalkthruRSS' -or `$request['WalkthruRSS']) { $($walkthruRssHandler.ToString().Replace('"','""')) } elseif (`$request.QueryString.ToString() -ieq '-ModuleRSS' -or `$request['ModuleRss'] -or `$request['Rss']) { $($moduleFeedHandler.ToString().Replace('"','""')) } elseif (`$Request['GetHelp']) { $($helpHandler.ToString().Replace('"','""')) } elseif (`$Request['Command']) { $($commandHandler.ToString().Replace('"','""')) } elseif (`$request['AnythingGoes']) { $($anythingHandler.ToString().Replace('"','""')) } $tableHandlers $checkoutHandlers $userTableHandlers $mailHandlers elseif (`$request.QueryString.ToString() -eq '-Download') { `$page = New-WebPage -Css `$cssStyle -Title ""`$(`$module.Name) | Download"" -AnalyticsID '$analyticsId' -RedirectTo '?-DownloadNow' `$response.Write(`$page ) } elseif (`$request.QueryString.ToString() -eq '-Me' -or `$request['ShowMe']) { $($meHandler.ToString().Replace('"', '""')) } elseif (`$request.QueryString.ToString() -eq '-DownloadProxyModule') { $($installMeHandler.ToString().Replace('"', '""')) } elseif (`$request.QueryString.Tostring() -eq '-GetPSD1' -or `$request['PSD1']) { `$baseUrl = `$request.URL `$response.ContentType = 'text/plain' `$response.Write([string]"" `$((Get-Content `$psd1Path -ErrorAction SilentlyContinue) -join ([Environment]::NewLine)) "") } elseif (`$request.QueryString.Tostring() -eq '-GetManifest' -or `$request['GetManifest']) { $($getManifestXmlHandler.ToString().Replace('"','""')) } elseif (`$request.QueryString.Tostring() -eq '-Sitemap' -or `$request['GetSitemap']) { $($getSitemapHandler.ToString().Replace('"','""')) } elseif (`$request.QueryString.Tostring() -eq '-Css' -or `$request.QueryString.Tostring() -eq '-Style') { if (`$pipeworksManifest -and `$pipeworksManifest.Style) { `$outcss = Write-CSS -NoStyleTag -Css `$pipeworksManifest.Style `$response.ContentType = 'text/css' `$response.Write([string]`$outCss) } else { `$response.ContentType = 'text/plain' `$response.Write([string]'') } } elseif (`$request.QueryString.ToString() -eq '-DownloadNow' -or `$request['DownloadNow']) { if (`$downloadUrl) { `$page = New-WebPage -Title ""Download `$(`$module.Name)"" -RedirectTo ""`$downloadUrl"" `$response.Write([string]`$page) } elseif (`$allowDownload) { `$modulezip = `$module.name + '.' + `$module.Version + '.zip' `$page = (New-object PSObject -Property @{RedirectTo=`$modulezip;RedirectIn='0:0:0.50'}),(New-object PSObject -Property @{RedirectTo=""/"";RedirectIn='0:0:7'}) | New-WebPage `$response.Write([string]`$page) `$response.Flush() } } elseif (`$request.QueryString.Tostring() -eq '-PaypalIPN' -or `$request['PayPalIPN']) { $($payPalIpnHandler.ToString().Replace('"','""')) } else { ` "@ + $coreModuleHandler.ToString().Replace('"', '""') + @" $(if ('continue', 'inquire' -contains $DebugPreference) { @" if (`$request['Timings'] -or `$request.Headers['Timings']) { `$timings = Get-Variable timeSpent* | Foreach-Object -Process { ""`$(`$_.Name)-`$(`$_.Value)"" } `$response.Headers.Add(""Timings"", ""`$Timings"") } "@ }) } ", context, null, false, $((-not $IsolateRunspace).ToString().ToLower())); "@ $moduleAshxInsteadOfDefault = $psBoundParameters.StartOnCommand -or $psBoundParameters.AsBlog $AcceptAnyUrl= $true if ($pipeworksManifest.AcceptanyUrl) { $AcceptAnyUrl= $true } if ($pipeworksManifest.DomainSchematics -and -not $PipeworksManifest.Stealth) { $firstdomain = $pipeworksManifest.DomainSchematics.GetEnumerator() | Sort-Object Key | Select-Object -First 1 -ExpandProperty Key $firstdomain = $firstdomain -split "\|" | ForEach-Object { $_.Trim() } | Select-Object -First 1 $x = & $NewSiteMap "http://$firstdomain" $x.Save("$outputDirectory\sitemap.xml") $x = & $NewRobotsTxt "http://$firstdomain" [IO.File]::WriteAllText("$outputDirectory\robots.txt", $x) } $importsPipeworks = $module.Name -eq 'Pipeworks' -or $module.RequiredModules -like "Pipeworks" if (-not $importsPipeworks) { Write-Progress "Determining Included Commands" " " $commandTokens= Get-Variable -Name *handler | Where-Object {$_.Value -is [ScriptBlock] } | ForEach-Object { [Management.Automation.PSParser]::Tokenize($_.Value, [ref]$null) } $loadedCommands = @{} $loadCommandQueue = New-Object Collections.Queue $commandsUsedInHandler = @($commandTokens + $tokensInPages | Where-Object { $_.Type -eq 'Command' } | Select-Object -ExpandProperty Content -Unique | Get-Command -Type Function -ErrorAction SilentlyContinue | ForEach-Object { $loadedCommands[$_.Name] = $_ $null = $loadCommandQueue.Enqueue($_) }) $loadedCommandCount = $loadedCommands.Count do { if ($loadCommandQueue.Count -eq 0) { break } $loadedCmd = $loadCommandQueue.Dequeue() $commandTokens= [Management.Automation.PSParser]::Tokenize($loadedCmd.Definition, [ref]$null) @($commandTokens | Where-Object { $_.Type -eq 'Command' } | Select-Object -ExpandProperty Content -Unique | Get-Command -Type Function -ErrorAction SilentlyContinue | ForEach-Object { if (-not ($loadedCommands[$_.Name]) -and -not ($loadCommandQueue -like "$($_.Name)")) { $loadedCommands[$_.Name] = $_ $null = $loadCommandQueue.Enqueue($_) } }) } while ($loadCommandQueue.Count) $commandsUsedInHandler = @('Write-Link', 'Write-CSS', 'Get-Walkthru', 'Get-Person', 'ConvertFrom-Markdown', 'Get-Hash', 'Out-HTML', 'New-Region', 'New-WebPage', 'Write-Ajax') + @($loadedCommands.Values | Where-Object { $_.Module.Name -eq 'Pipeworks' } | Select-Object -ExpandProperty Name ) $commandsUsedInHandler = ($commandsUsedInHandler | Select-Object -Unique) } #region CommonPageCodeBehind if ($usesDynamicPages) { $embedSection = "" if (-not $ImportsPipeworks) { $commandsUsedInHandler = $commandsUsedInHandler | Select-Object -Unique } else { $commandsUsedInHandler = 'Out-HTML' } $embedSection += foreach ($func in (Get-Command -Name $commandsUsedInHandler -CommandType Function)) { @" string compressed$($func.Name.Replace('-', ''))Defintion = "$(Compress-Data -String $func.Definition.ToString())"; byte[] binaryDataFor$($func.Name.Replace('-', '')) = System.Convert.FromBase64String(compressed$($func.Name.Replace('-', ''))Defintion); System.IO.MemoryStream memoryStreamFor$($func.Name.Replace('-', '')) = new System.IO.MemoryStream(); memoryStreamFor$($func.Name.Replace('-', '')).Write(binaryDataFor$($func.Name.Replace('-', '')), 0, binaryDataFor$($func.Name.Replace('-', '')).Length); memoryStreamFor$($func.Name.Replace('-', '')).Seek(0, 0); System.IO.Compression.GZipStream decompressorFor$($func.Name.Replace('-', '')) = new System.IO.Compression.GZipStream(memoryStreamFor$($func.Name.Replace('-', '')), System.IO.Compression.CompressionMode.Decompress); System.IO.StreamReader readerFor$($func.Name.Replace('-', '')) = new System.IO.StreamReader(decompressorFor$($func.Name.Replace('-', ''))); string decompressedDefinitionFor$($func.Name.Replace('-', '')) = readerFor$($func.Name.Replace('-', '')).ReadToEnd(); SessionStateFunctionEntry $($func.Name.Replace('-',''))Command = new SessionStateFunctionEntry( "$($func.Name)", decompressedDefinitionFor$($func.Name.Replace('-', '')) ); iss.Commands.Add($($func.Name.Replace('-',''))Command); "@ } $codeBehind = @" using System; using System.Web.UI; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Collections; using System.Collections.ObjectModel; public partial class PowerShellPage : Page { public InitialSessionState InitializeRunspace() { InitialSessionState iss = InitialSessionState.CreateDefault(); $embedSection string[] commandsToRemove = new String[] { "$($functionBlacklist -join '","')"}; foreach (string cmdName in commandsToRemove) { iss.Commands.Remove(cmdName, null); } return iss; } public void RunScript(string script) { bool shareRunspace = $((-not $IsolateRunspace).ToString().ToLower()); UInt16 poolSize = $PoolSize; PowerShell powerShellCommand = PowerShell.Create(); bool justLoaded = false; PSInvocationSettings invokeNoHistory = new PSInvocationSettings(); invokeNoHistory.AddToHistory = false; Collection<PSObject> results; if (shareRunspace) { if (Application["RunspacePool"] == null) { justLoaded = true; RunspacePool rsPool = RunspaceFactory.CreateRunspacePool(InitializeRunspace()); rsPool.SetMaxRunspaces($PoolSize); rsPool.ApartmentState = System.Threading.ApartmentState.STA; rsPool.ThreadOptions = PSThreadOptions.ReuseThread; rsPool.Open(); powerShellCommand.RunspacePool = rsPool; Application.Add("RunspacePool",rsPool); // Initialize the pool Collection<IAsyncResult> resultCollection = new Collection<IAsyncResult>(); for (int i =0; i < $poolSize; i++) { PowerShell execPolicySet = PowerShell.Create(). AddScript(@" Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force ", false); execPolicySet.RunspacePool = rsPool; resultCollection.Add(execPolicySet.BeginInvoke()); } foreach (IAsyncResult lastResult in resultCollection) { if (lastResult != null) { lastResult.AsyncWaitHandle.WaitOne(); } } powerShellCommand.Commands.Clear(); } powerShellCommand.RunspacePool = Application["RunspacePool"] as RunspacePool; string newScript = @"param(`$Request, `$Response, `$Server, `$session, `$Cache, `$Context, `$Application, `$JustLoaded, `$IsSharedRunspace, [Parameter(ValueFromRemainingArguments=`$true)]`$args) if (`$request -and `$request.Params -and `$request.Params['PATH_TRANSLATED']) { Split-Path `$request.Params['PATH_TRANSLATED'] | Set-Location } " + script; powerShellCommand.AddScript(newScript, false); powerShellCommand.AddParameter("Request", Request); powerShellCommand.AddParameter("Response", Response); powerShellCommand.AddParameter("Session", Session); powerShellCommand.AddParameter("Server", Server); powerShellCommand.AddParameter("Cache", Cache); powerShellCommand.AddParameter("Context", Context); powerShellCommand.AddParameter("Application", Application); powerShellCommand.AddParameter("JustLoaded", justLoaded); powerShellCommand.AddParameter("IsSharedRunspace", true); results = powerShellCommand.Invoke(); } else { Runspace runspace; if (Session["UserRunspace"] == null) { Runspace rs = RunspaceFactory.CreateRunspace(InitializeRunspace()); rs.ApartmentState = System.Threading.ApartmentState.STA; rs.ThreadOptions = PSThreadOptions.ReuseThread; rs.Open(); powerShellCommand.Runspace = rs; powerShellCommand. AddCommand("Set-ExecutionPolicy", false). AddParameter("Scope", "Process"). AddParameter("ExecutionPolicy", "Bypass"). AddParameter("Force", true). Invoke(null, invokeNoHistory); powerShellCommand.Commands.Clear(); Session.Add("UserRunspace",rs); justLoaded = true; } runspace = Session["UserRunspace"] as Runspace; if (Application["Runspaces"] == null) { Application["Runspaces"] = new Hashtable(); } if (Application["RunspaceAccessTimes"] == null) { Application["RunspaceAccessTimes"] = new Hashtable(); } if (Application["RunspaceAccessCount"] == null) { Application["RunspaceAccessCount"] = new Hashtable(); } Hashtable runspaceTable = Application["Runspaces"] as Hashtable; Hashtable runspaceAccesses = Application["RunspaceAccessTimes"] as Hashtable; Hashtable runspaceAccessCounter = Application["RunspaceAccessCount"] as Hashtable; if (! runspaceTable.Contains(runspace.InstanceId.ToString())) { runspaceTable[runspace.InstanceId.ToString()] = runspace; } if (! runspaceAccessCounter.Contains(runspace.InstanceId.ToString())) { runspaceAccessCounter[runspace.InstanceId.ToString()] = 0; } runspaceAccessCounter[runspace.InstanceId.ToString()] = ((int)runspaceAccessCounter[runspace.InstanceId.ToString()]) + 1; runspaceAccesses[runspace.InstanceId.ToString()] = DateTime.Now; runspace.SessionStateProxy.SetVariable("Request", Request); runspace.SessionStateProxy.SetVariable("Response", Response); runspace.SessionStateProxy.SetVariable("Session", Session); runspace.SessionStateProxy.SetVariable("Server", Server); runspace.SessionStateProxy.SetVariable("Cache", Cache); runspace.SessionStateProxy.SetVariable("Context", Context); runspace.SessionStateProxy.SetVariable("Application", Application); runspace.SessionStateProxy.SetVariable("JustLoaded", justLoaded); runspace.SessionStateProxy.SetVariable("IsSharedRunspace", false); powerShellCommand.Runspace = runspace; powerShellCommand.AddScript(@" `$timeout = (Get-Date).AddMinutes(-20) `$oneTimeTimeout = (Get-Date).AddMinutes(-1) foreach (`$key in @(`$application['Runspaces'].Keys)) { if ('Closed', 'Broken' -contains `$application['Runspaces'][`$key].RunspaceStateInfo.State) { `$application['Runspaces'][`$key].Dispose() `$application['Runspaces'].Remove(`$key) continue } if (`$application['RunspaceAccessTimes'][`$key] -lt `$Timeout) { `$application['Runspaces'][`$key].CloseAsync() continue } } ").Invoke(null, invokeNoHistory); powerShellCommand.Commands.Clear(); powerShellCommand.AddCommand("Split-Path", false).AddParameter("Path", Request.ServerVariables["PATH_TRANSLATED"]).AddCommand("Set-Location").Invoke(null, invokeNoHistory); powerShellCommand.Commands.Clear(); results = powerShellCommand.AddScript(script, false).Invoke(); } foreach (Object obj in results) { if (obj != null) { if (obj is IEnumerable) { if (obj is String) { Response.Write(obj); } else { IEnumerable enumerableObj = (obj as IEnumerable); foreach (Object innerObject in enumerableObj) { if (innerObject != null) { Response.Write(innerObject); } } } } else { Response.Write(obj); } } } foreach (ErrorRecord err in powerShellCommand.Streams.Error) { Response.Write("<span class='ErrorStyle' style='color:red'>" + err + "<br/>" + err.InvocationInfo.PositionMessage + "</span>"); } powerShellCommand.Dispose(); } } "@ | Set-Content "$outputDirectory\PowerShellPageBase.cs" } #endregion CommonPageCodeBehind #region Module Output if ($pipeworksManifest.PoolSize -as [uint32]) { $poolSize = $pipeworksManifest.PoolSize } $newDefaultExtensions = Get-ChildItem $outputDirectory -Filter default.* | Select-Object -ExpandProperty Extension $handlerText = & $writeSimpleHandler -PoolSize:$PoolSize -sharerunspace:(-not $isolateRunspace) -csharp $moduleHandler -ImportsPipeworks:$importsPipeworks -EmbeddedCommand $commandsUsedInHandler $defaultFile = if ($newDefaultExtensions -contains '.aspx') { $moduleAshxInsteadOfDefault = $true [IO.File]::WriteAllText("$outputDirectory\Module.ashx", $handlerText) } elseif ($newDefaultExtensions -contains '.html') { "default.html" } elseif ($newDefaultExtensions -contains '.ashx') { "default.ashx" } else { if ($AcceptAnyUrl) { $null } else { "default.ashx" } } if ($moduleAshxInsteadOfDefault) { if ($psBoundParameters.AsBlog) { Copy-Item "$outputDirectory\Blog.html" "$outputDirectory\Default.htm" } [IO.File]::WriteAllText("$outputDirectory\Module.ashx", $handlerText) } else { [IO.File]::WriteAllText("$outputDirectory\Module.ashx", $handlerText) $defaultFile = "Module.ashx" #[IO.File]::WriteAllText("$outputDirectory\Default.ashx", $handlerText) } #endregion Module Output #region Module Nesting # In some cases, one might want to publish multiple modules to different sublocations at the same time. # A good example would be having a main site, a blog, and an online store. # In order to nest, make a Pipeworks Manifest section called "Nest", "Nested", or "NestedModules". # This section will be a hashtable, # The key will be the subdirectory where the nested module will be placed. # The value will contain the module placed in the subdirectory. # It can optionally be followed by a : and a comma-separated list of schematics. # It can also start with a : and contain the schematics that will be published. # If this occurs, the current module will be published to the subdirectory, using the specified schematics. # For example: # @{Blog='Start-Scripting:Blog'} # nest the module Start-Scripting to the subdirectory blog. $nestedModules = @($pipeworksManifest.Nest) + $pipeworksManifest.Nested + $pipeworksManifestPath.NestedModule + $pipeworksManifest.NestedModules $nestedModules = @($nestedModules -ne $null ) foreach ($nested in $nestedModules) { if ($nested -isnot [Hashtable]) { continue } foreach ($ni in $nested.GetEnumerator()) { $nestedOutputDirectory = Join-Path $OutputDirectory $ni.Key $nestedModule = if ($ni.Value -like ":*") { $realModule.Name } else { @($ni.Value -split ":")[0] } $nestedSchematic = if ($ni.Value -notlike "*:*") { @("Default") } else { @(@($ni.Value -split ":")[1] -split "," -ne '') } $nestedSchematic = @(foreach ($ns in $nestedSchematic) { $ns.Trim() }) ConvertTo-ModuleService -OutputDirectory $nestedOutputDirectory -Name $nestedModule -UseSchematic $nestedSchematic -Force -IsNested } } # 11/2/2013 #endregion Module Nesting #region Configuration Settings $configSettingsChunk = '' if ($ConfigSetting.Count) { $configSettingsChunk = "<appSettings>" + (@(foreach ($kv in $configSetting.GetEnumerator()) {" <add key='$($kv.Key)' value='$($kv.Value)'/>" }) -join ('')) + "</appSettings>" } $acceptAnyUrl = $true if ($pipeworksManifest.MaximumRequestLength) { $maximumRequestLength = $pipeworksManifest.MaximumRequestLength } $realMax = [Math]::Ceiling(($maximumRequestLength / 1kb)) if ($pipeworksManifest.ExecutionTimeout -as [timespan]) { $ExecutionTimeout = $pipeworksManifest.ExecutionTimeout -as [timespan] } $cacheControl = " <staticContent> <clientCache cacheControlMode='UseMaxAge' cacheControlMaxAge='$($CacheStaticContentFor)' /> </staticContent> " $runTimeChunk =" <httpRuntime executionTimeout='$($executionTimeout.TotalSeconds -as [uint32])' maxRequestLength='$realMax' useFullyQualifiedRedirectUrl='false' appRequestQueueLimit='100' enableVersionHeader='false' />" $childDirectories = Get-ChildItem -Path $OutputDirectory | Where-Object { $_.PSIsContainer } $excludeChildDirectories = foreach ($child in $childDirectories) { @" <add input="{URL}" pattern="^$($child.Name)/" negate="true" /> <add input="{URL}" pattern="^$($child.Name)$" negate="true" /> "@ } $rewriteUrlChunk = "<rewrite> <rules> <rule name='RewriteAll_For$($psBoundParameters.Name + $(if ($psBoundParameters.UseSchematic) { "_$($psBoundParameters.UseSchematic)" }))'> <match url='.*' /> <conditions logicalGrouping='MatchAll'> <add input='{REQUEST_FILENAME}' matchType='IsDirectory' negate='true' /> $excludeChildDirectories <add input='{URL}' pattern='^.*\.(ashx|axd|css|gif|png|ico|jpg|jpeg|js|flv|f4v|zip|xlsx|docx|mp3|mp4|xml|html|htm|aspx|php|pdf)$' negate='true' /> </conditions> <action type='Rewrite' url='AnyUrl.aspx' /> </rule> </rules> </rewrite>" if (-not $AcceptAnyUrl) { $rewriteUrlChunk = "" } else { if (-not (Test-Path "$outputDirectory\AnyUrl.aspx")) { $rewriteUrlChunk = $rewriteUrlChunk.Replace("AnyUrl.aspx", "Module.ashx?AnythingGoes=true") } } # $rewriteUrlChunk= "" $defaultFound = ( (Join-Path (Split-Path $OutputDirectory) "web.config") | Get-Content -path { $_ } -ErrorAction SilentlyContinue | Select-String defaultDocument ) -as [bool] if ($Isnested) { $defaultFound = $true } $defaultDocumentChunk = if ((-not ($defaultFile))) { @" <system.webServer> $(if (-not $defaultFound) { @" <defaultDocument> <files> <add value="default.ashx" /> </files> </defaultDocument> "@}) $rewriteUrlChunk $cacheControl </system.webServer> "@ } else { @" <system.webServer> $(if (-not $defaultFound) { @" <defaultDocument> <files> <add value="${defaultFile}" /> </files> </defaultDocument> "@}) $rewriteUrlChunk $cacheControl </system.webServer> "@ } $null = $null $customErrorPages = @($pipeworksManifest.Pages.Keys) -like "???.*" $AlwaysGenerateErrorPages = @{ "400" = "Bad Request" "401" = "Sorry, no admittance" "403" = "Sorry, no admittance" "404" = "This is not the web page you were looking for" "408" = "That took too long" "450" = "Sorry, kid. Mom and Dad don't want you visiting this page" "451" = "Lawyers have informed us that we can't show this page" } $customErrorsSection = " " $customErrorsSection += foreach ($cep in $customErrorPages) { $cepNumber = "$cep".Substring(0, 3) -as [uint32] if (-not $cepNumber) { continue} if ($AlwaysGenerateErrorPages.$cepNumber) { $null =$AlwaysGenerateErrorPages.Remove($cepNumber) } "<error statusCode='$cepNumber' redirect='~$Cep' /> " } $customErrorsSection += foreach ($k in $AlwaysGenerateErrorPages.Keys) { $titleArea = if ($PipeworksManifest -and $pipeworksManifest.Logo) { "<a href='$linkUrl' class='brand'><img src='$($pipeworksManifest.Logo)' alt='$($module)' style='border:0' /></a>" } else { "<a href='$linkUrl' class='brand'>$($Module.Name)</a>" } " <div style='width:100%;height:100%'> <div style='width:50%;margin-left:auto;margin-right:auto;text-align:center'> $titleArea </div> <br style='line-height:500%' /> <h1 style='width:75%;margin-left:auto;margin-right:auto;text-align:center'> $($AlwaysGenerateErrorPages[$k]) </h1> </div> " | New-WebPage -Title $AlwaysGenerateErrorPages[$k] | Set-Content "$OutputDirectory\$k.html" "<error statusCode='$k' redirect='~$($k).html' /> " } $webConfig = @" <configuration> $ConfigSettingsChunk $defaultDocumentChunk $net4Compat <system.web> <customErrors mode="On"> $customErrorsSection </customErrors> $runTimeChunk </system.web> </configuration> "@ $webConfig | Set-Content "$outputDirectory\web.config" # If there's a user DB, make sure that it exists if ($pipeworksManifest.UserDB.Name -and $pipeworksManifest.UserDB.ConnectionSetting) { $tableExists = Get-SQLTable -TableName $pipeworksManifest.UserDB.Name -ConnectionStringOrSetting $pipeworksManifest.UserDB.ConnectionSetting if (-not $tableExists) { Add-SqlTable -RowKey "UserId" -TableName $pipeworksManifest.UserDB.Name -Column UserEmail -ConnectionStringOrSetting $pipeworksManifest.UserDB.ConnectionSetting -ErrorVariable CreateTableIssues if ($createTableIssues) { Write-Warning "The User table did not exist in SQL, and count not be created" } } } # If nothing on the page requires a login, "bake" the finished page. This will only work if the output directory is beneath the WWWRoot of the local server. $anyCommandRequiresLogin = $pipeworksManifest.WebCommand.Values | Where-Object { $_.RequiresLogin -or $_.RequireLogin -or $_.ValidUserTable -or $_.IfLoggedInAs } $anyCommandRunsWithoutInput = if ($pipeworksManifest.WebCommand) { $pipeworksManifest.WebCommand.GetEnumerator() | Where-Object { $_.Value.RunWithoutInput -and -not $_.Value.Hidden -and ($pipeworksManifest.Inline -eq '*' -or $pipeworksManifest.Inline -eq $_.Key) } } else { $null } $thereIsADefaultCommand = $pipeworksManifest.DefaultCommand $noServices = $PipeworksManifest.WebCommand -as [bool] $shouldBakePages = $true if ($anyCommandRequiresLogin -or $anyCommandRunsWithoutInput -or $thereIsADefaultCommand) { $shouldBakePages = $false } if (($newDefaultExtensions -like '.aspx') -and (-not $pipeworksManifest.BakePage)) { $shouldBakePages = $false } if ($newDefaultExtensions -like ".htm*") { $shouldBakePages = $false } if ($shouldBakePages) { if ($newDefaultExtensions -like '.aspx') { } Write-Progress "Baking Module Page" " " #$cssStyle = $((Write-PowerShellHashtable $Style)) $HalfMarginPercentLeftString = "$(($MarginPercentLeftString.Replace('%', '') -as [double])/2)%" $HalfMarginPercentRightString = "$(($MarginPercentRightString.Replace('%', '') -as [double])/2)%" $MarginPercentLeftString = $MarginPercentLeftString.Trim() $MarginPercentRightString = $MarginPercentRightString.Trim() $DownloadUrl = '$DownloadUrl' $analyticsId = '$analyticsId' $modulemaker = $module.CompanyName $bakingPage = $true $null = . ([ScriptBlock]::Create($embedCommand)) $null = . ([ScriptBlock]::Create($initModuleDefaults)) $null = . ([ScriptBlock]::Create($getModuleMetaData)) $pipeworksManifest = @{} + $Global:pipeworksManifest $bakedDefault = . $coreModuleHandler $global:PipeworksManifest = $null $bakingPage = $false #$bakedDefault = Get-Web -Url "http://localhost/$realModule/" -UseWebRequest -Timeout 01:00:00 if ($bakedDefault) { [IO.File]::WriteAllText("$outputDirectory/Default.htm", $bakedDefault) $defaultDocumentChunk = @" <system.webServer> $rewriteUrlChunk $cacheControl </system.webServer> "@ $webConfig = @" <configuration> $ConfigSettingsChunk $defaultDocumentChunk $net4Compat <system.web> <customErrors mode="On"> $customErrorsSection </customErrors> $runTimeChunk </system.web> </configuration> "@ $webConfig | Set-Content "$outputDirectory\web.config" } } if ($AsIntranetSite) { Import-Module WebAdministration -Global -Force $allSites = Get-Website $AlreadyExists = $allSites | Where-Object {$_.Name -eq "$Name" } if (-not $alreadyExists) { $targetPort = $Port $portIsOccupied = $null do { if (-not $targetPort) { $targetPort = 80 } else { $oldTargetPort = $targetPort if ($portIsOccupied) { $targetPort = Get-Random -Maximum 64kb Write-Warning "Port $oldTargetPort occupied, trying Port $targetPort" } } $portIsOccupied = Get-Website | Where-Object { $_.Bindings.Collection | Where-Object { $_-like "*:$targetPort*" } } } while ($portIsOccupied) $w = New-Website -Name "$Name" -Port $targetPort -PhysicalPath $outputDirectory -Force $AlreadyExists = Get-Website | Where-Object {$_.Name -eq "$Name" } } if ($Realm) { Set-WebConfigurationProperty -filter /system.webServer/security/authentication/anonymousAuthentication -name enabled -value false -PSPath IIS:\ -Location $Name Set-WebConfigurationProperty -filter /system.webServer/security/authentication/windowsAuthentication -name enabled -value true -PSPath IIS:\ -Location $Name } else { Set-WebConfigurationProperty -filter /system.webServer/security/authentication/anonymousAuthentication -name enabled -value false -PSPath IIS:\ -Location $Name Set-WebConfigurationProperty -filter /system.webServer/security/authentication/windowsAuthentication -name enabled -value true -PSPath IIS:\ -Location $Name } if ($appPoolCredential) { $appPool = Get-Item "IIS:\AppPools\${name}AppPool" -ErrorAction SilentlyContinue if (-not $appPool) { $pool = New-WebAppPool -Name "${name}AppPool" -Force $appPool = Get-Item "IIS:\AppPools\${name}AppPool" -ErrorAction SilentlyContinue } $appPool.processModel.userName = $appPoolCredential.username $appPool.processModel.password = $appPoolCredential.GetNetworkCredential().password $appPool.processModel.identityType = 3 $appPool | Set-Item $AlreadyExists = Get-Website | Where-Object {$_.Name -eq "$Name" } $siteInf = Get-ChildItem Set-ItemPRoperty iis:\sites\$name -Name ApplicationPool -Value "${name}AppPool" -Force } } #region Global.asax Session Cleanup @' <%@ Assembly Name="System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %> <script language="C#" runat="server"> public void Session_OnEnd() { System.Management.Automation.Runspaces.Runspace rs = Session["User_Runspace"] as System.Management.Automation.Runspaces.Runspace; if (rs != null) { rs.Close(); rs.Dispose(); } System.GC.Collect(); } </script> '@ | Set-Content "$outputDirectory\Global.asax" #endregion } Pop-Location if ($IISReset) { iisreset /noforce | ForEach-Object { Write-Progress "Resetting IIS" "$_ " } } if ($Show) { if ($port) { Start-Process -FilePath "http://localhost:$Port/" } else { Start-Process -FilePath "http://localhost/$Module/" } } if ($do) { if (-not $do.DnsSafeHost) { if ($port) { Start-Process -FilePath "http://localhost:$Port/$("$Do".TrimStart('/'))" } else { Start-Process -FilePath "http://localhost/$Module/$("$Do".TrimStart('/'))" } } else { Start-Process -FilePath "$Do" } } #endregion Configuration Settings } } |