Use-Schematic.ps1

 function Use-Schematic
{
    <#
    .Synopsis
        Uses a schematic to put things together
    .Description
        Uses a schematic to automate the deployment
    .Link
        ConvertTo-ModuleService
    #>

    param(
    # The name of the schematic
    [Parameter(Mandatory=$true,Position=0)]
    [string]
    $SchematicName,
    
    # The name of the module that the schematic will be used on.
    # This will also determine the default input and output directories
    [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
    [Alias('Name')]
    [string]
    $ModuleName,

    # Parameters to the schematic.
    # These will be merged with any parameters provided from the module.
    [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
    [Hashtable]
    $Parameter,

    # A complete pipeworks manifest.
    # This will be merged with the pipeworks manifest from the module, if a module is provided
    [Parameter(Position=3,ValueFromPipelineByPropertyName=$true)]
    [Hashtable]
    $Manifest,

    # A custom output directory.
    # This will override the default module output directory (the module path).
    # If no module is provided, the current directory will be used as the input directory.
    [Parameter(Position=4,ValueFromPipelineByPropertyName=$true)]
    [string]
    $InputDirectory,

    # A custom output directory.
    # This will override the default module output directory (\inetpub\wwwroot\ModuleName)
    # If no module is provided, the output directory will default to \inetpub\wwwroot
    [Parameter(Position=5,ValueFromPipelineByPropertyName=$true)]
    [string]
    $OutputDirectory        
    )


    process {
        
        $MypipeworksManifest  = @{}

        if ($ModuleName) {
            $module = $realModule = Get-Module $moduleName
            if (-not $realModule) { return } 

            # Import pipeworks manifest
            $moduleRoot = Split-Path $realModule.Path                     
            #region Initialize Pipeworks Manifest
            $pipeworksManifestPath = Join-Path $moduleRoot "$($realmodule.Name).Pipeworks.psd1"
            $MypipeworksManifest  = 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" 
                    return
                }                                                
            }



            if (-not $PSBoundParameters.InputDirectory) {
                $inputDirectory = $moduleRoot
            }

            if (-not $PSBoundParameters.OutputDirectory) {
                $OutputDirectory = "$Env:SystemDrive\inetpub\wwwroot\$($realModule.Name)"
            }
            
        }

        if ($manifest) {
            foreach ($kv in $Manifest.GetEnumerator()) {
                $MypipeworksManifest[$kv.Key] = $kv.Value
            }
        }

        if ($parameter) {
            if (-not $MyPipewokrsManifest["$parameter"]) {
                $MyPipeworksManifest["$Parameter"]
            }
        }

        if (-not $inputDirectory) {
            $inputDirectory = "$pwd"
        }

        if (-not $OutputDirectory) {
            $outputDirectory = "$Env:SystemDrive\inetpub\wwwroot"
        }

        $schematic = $SchematicName
        $moduleList =  
            if ($realModule ) {
                (@($realModule) + @($Realmodule.RequiredModules) + @(Get-Module Pipeworks))
            } else {
                @(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 $mypipeworksManifest.$schematic) {
                    Write-Error "Missing $schematic schematic parameters for $($realmodule.Name)"
                    continue
                }
                $pagesToMerge = & {                            
                    . $s.Fullname
                    $schematicCmd = 
                        Get-Command -Verb Use -Noun *Schematic | 
                        Where-Object {$_.Name -ne 'Use-Schematic'} | 
                        Select-Object -First 1 
                        
                    $schematicParameters = @{
                        Parameter = $mypipeworksManifest.$schematic
                        Manifest = $myPipeworksManifest 
                        DeploymentDirectory = $outputDirectory 
                        inputDirectory = $inputDirectory
                    }
                    if ($schematicCmd.Name) {
                        & $schematicCmd @schematicParameters
                        Remove-Item "function:\$($schematicCmd.Name)"
                    }
                }
                    
                if ($pagesToMerge) {
                    foreach ($kv in $pagesToMerge.GetEnumerator()) {
                        $MypipeworksManifest.pages[$kv.Key] = $kv.Value
                    }                   
                }
            }                    
        }
                        
        #region Pages
        $IsolateRunspace = $true
        $PoolSize = 1
        #If the manifest declares additional web pages, create a page for each item
        if ($MyPipeworksManifest.Pages -and 
            $MyPipeworksManifest.Pages.GetType() -eq [Hashtable] ) {
            $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
`$pulseTimer = New-Object Timers.Timer -Property @{
    #Interval = ([Timespan]'$pulseInterval').TotalMilliseconds
}
 
 
Register-ObjectEvent -InputObject `$pulseTimer -EventName Elapsed -SourceIdentifier PipeworksPulse -Action {
    `$global:LastPulse = Get-Date
     
}
`$pulseTimer.Start()
 
 
 
", 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"
            $null = $codeBehind
            foreach ($pageAndContent in $MyPipeworksManifest.Pages.GetEnumerator()) {
                
                $pageName = $pageAndContent.Key 
                Write-Progress "Creating Pages" "$pageName"
                $safePageName = $pageName.Replace("|", " ").Replace("/", "-").Replace(":","-").Replace("!", "-").Replace(";", "-").Replace(" ", "_").Replace("@","at")
                $pageContent = $pageAndContent.Value
                $realPageContent = 
                    if ($pageContent -is [Hashtable]) {                    
                        if (-not $pageContent.Css -and $MypipeworksManifest.Style) {
                            $pageContent.Css = $MypipeworksManifest.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   | 
                                    Add-Member NoteProperty IsPsPage $true -PassThru
                            }         
                        }
                    } 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*"){
                        # .HTML files
                        $pagePath = Join-Path $moduleRoot $pageContent.TrimStart(".\")
                        if (Test-Path $pagePath) {
                            try {
                                $potentialPagecontent = [IO.File]::ReadAllText($pagePath)                                
                                $pageContent = $potentialPagecontent 
                            } catch {
                                $_ | Write-Error
                            }
                        }
                    } else {
                        $pageContentAsScriptBlock = try { [ScriptBlock]::Create($pageContent) } catch { } 
                        if ($pageContentAsScriptBlock) {
                            & $pageContentAsScriptBlock
                        } else {
                            $pageContent
                        }
                    }
                
              
                
                if ($realPageContent.IsPsPage) {
                    $safePageName = $safePageName.Replace(".pspage", "").Replace(".pspg", "")
                    $parentPath = $safePageName | Split-Path
                    if (-not (Test-Path "$outputDirectory\$parentPath")) {
                        $null = New-Item -ItemType Directory -Path "$outputDirectory\$parentPath"
                    }
                    $realPageContent | 
                        Set-Content "$outputDirectory\${safepageName}.aspx"
                } 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
    }       
}