Skip to content

Instantly share code, notes, and snippets.

@JanLenoch
Created September 26, 2016 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JanLenoch/2efa133553319b1c10bf56a804b89dd9 to your computer and use it in GitHub Desktop.
Save JanLenoch/2efa133553319b1c10bf56a804b89dd9 to your computer and use it in GitHub Desktop.
Get Near-Zero Load Time in Both Development and Production (Advanced)
$Paths = @("C:\inetpub\wwwroot\Kentico9InVirtualApplication\CMS\;1;/Kentico9App", "C:\inetpub\Kentico9InSite\CMS\;2")
$ScriptBlockBase = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_compiler.exe -m LM/W3SVC/"
$JobNamePrefix = "KenticoPrecompilationJob"
$ConfirmPreference = "none"
# The self-elevaing logic was developed by Jonathan Bennett: https://www.autoitscript.com/forum/topic/174609-powershell-script-to-self-elevate/
# Test if admin
function Test-IsAdmin()
{
    # Get the current ID and its security principal
    $windowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $windowsPrincipal = new-object System.Security.Principal.WindowsPrincipal($windowsID)
 
    # Get the Admin role security principal
    $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
 
    # Are we an admin role?
    if ($windowsPrincipal.IsInRole($adminRole))
    {
        $true
    }
    else
    {
        $false
    }
}
# Get UNC path from mapped drive
function Get-UNCFromPath
{
   Param(
    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
    [String]
    $Path)
    if ($Path.Contains([io.path]::VolumeSeparatorChar))
    {
        $psdrive = Get-PSDrive -Name $Path.Substring(0, 1) -PSProvider 'FileSystem'
        # Is it a mapped drive?
        if ($psdrive.DisplayRoot)
        {
            $Path = $Path.Replace($psdrive.Name + [io.path]::VolumeSeparatorChar, $psdrive.DisplayRoot)
        }
    }
    return $Path
}
# Relaunch the script if not admin
function Invoke-RequireAdmin
{
    Param(
    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
    [System.Management.Automation.InvocationInfo]
    $MyInvocation)
    if (-not (Test-IsAdmin))
    {
        # Get the script path
        $scriptPath = $MyInvocation.MyCommand.Path
if ($scriptPath -eq "" -or $scriptPath -eq $null)
{
$scriptPath = (Get-Location).Path
}
        $scriptPath = Get-UNCFromPath -Path $scriptPath
        # Need to quote the paths in case of spaces
        $scriptPath = '"' + $scriptPath + '"'
        # Build base arguments for powershell.exe
        [string[]]$argList = @('-NoLogo -NoProfile', '-ExecutionPolicy Bypass', '-File', $scriptPath)
        # Add
        $argList += $MyInvocation.BoundParameters.GetEnumerator() | Foreach {"-$($_.Key)", "$($_.Value)"}
        $argList += $MyInvocation.UnboundArguments
        try
        {    
            $process = Start-Process PowerShell.exe -PassThru -Verb Runas -Wait -WorkingDirectory $pwd -ArgumentList $argList
            exit $process.ExitCode
        }
        catch {}
        # Generic failure code
        exit 1
    }
}
# Relaunch if not admin
Invoke-RequireAdmin $script:MyInvocation
# Here comes the precompilation logic
$Jobs = @()
$References = @(("CMS.Core.dll"), ("CMS.Base.dll"),("CMS.DataEngine.dll"),("CMS.DataProviderSQL.dll"),("CMS.Membership.dll"), ("CMS.UIControls.dll"))
$MarshallableCb = @"
using System;
using System.IO;
using System.Reflection;
using CMS.Core;
using CMS.Base;
using CMS.UIControls;
using CMS.UIControls.Internal;
public class KenticoCompilationMarshallable : MarshalByRefObject
{
public static AppDomain _kenticoPsAppDomain = null;
public static string _applicationBase = null;
public static AppDomain CreateKenticoPsAppDomain(string applicationBase)
{
AppDomainSetup appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationName = "KenticoPsAppDomain_" + Guid.NewGuid().ToString().GetHashCode().ToString("x");
_applicationBase = applicationBase;
appDomainSetup.ApplicationBase = _applicationBase;
_kenticoPsAppDomain = AppDomain.CreateDomain(appDomainSetup.ApplicationName, null, appDomainSetup);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(KenticoAssemblyResolve);
return _kenticoPsAppDomain;
}
public static Assembly KenticoAssemblyResolve(object sender, ResolveEventArgs args)
{
string[] Parts = args.Name.Split(',');
string File = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + Parts[0].Trim() + ".dll";
return Assembly.LoadFrom(File);
}
public static ConnectionStringService LoadBasicAssemblies(string assemblySubFolder)
{
ConnectionStringService BaseInstance = CreateObject<ConnectionStringService>(assemblySubFolder, "CMS.Base.dll", typeof(ConnectionStringService));
return BaseInstance;
}
private static TResult CreateObject<TResult>(string assemblySubFolder, string assemblyFileName, Type type)
{
return (TResult)_kenticoPsAppDomain.CreateInstanceFrom(_applicationBase + assemblySubFolder + assemblyFileName, type.FullName).Unwrap();
}
public static DeploymentManager CreateDeploymentManager(string assemblySubFolder, string assemblyFileName)
{
return CreateObject<DeploymentManager>(assemblySubFolder, assemblyFileName, typeof(DeploymentManager));
}
}
"@
function Build-MetabasePath($P)
{
$Split = $Path -split ";"
if ($Split[2] -eq $null)
{
return $Split[1] + "/ROOT"
}
else
{
return $Split[1] + "/ROOT" + $Split[2]
}
}
function Load-Assemblies($Path, $ExtensionMask)
{
$FolderContents = Get-ChildItem $Path -Filter $ExtensionMask -Recurse
foreach ($File in $FolderContents)
{
try
{
Add-Type -Path $File.FullName
}
catch
{
"Failed to load assembly: " + $File.BaseName + " " + $_.Exception.Message
}
}
}
$AssemblyResolveSb = {
[string[]]$PathParts = $EventArgs.Name.Split(",");
[string]$File = [System.IO.Path]::GetDirectoryName([System.Reflection.Assembly]::GetExecutingAssembly().Location) + "\" + $PathParts[0].Trim() + ".dll"
return [System.Reflection.Assembly]::LoadFrom($File)
}
function Compile-VirtualObjects($CmsPath)
{
$OutputFolderPath = $CmsPath + "bin\"
Load-Assemblies -Path ($CmsPath + "\CMSDependencies\") -ExtensionMask "*.dll"
Load-Assemblies -Path $OutputFolderPath -ExtensionMask "*.dll"
Add-Type -TypeDefinition $MarshallableCb -ReferencedAssemblies ($References | ForEach-Object {$OutputFolderPath + $_}) -IgnoreWarnings
$KenticoPsAppDomain = [KenticoCompilationMarshallable]::CreateKenticoPsAppDomain($CmsPath)
$KenticoPsAppDomain.Load("System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
[Configuration.ConfigurationManager].GetField("s_initState", "NonPublic, Static").SetValue($null, 0)
[Configuration.ConfigurationManager].GetField("s_configSystem", "NonPublic, Static").SetValue($null, $null)
([Configuration.ConfigurationManager].Assembly.GetTypes() | where {$_.FullName -eq "System.Configuration.ClientConfigPaths"})[0].GetField("s_current", "NonPublic, Static").SetValue($null, $null)
$KenticoPsAppDomain.AppendPrivatePath($OutputFolderPath);
Register-ObjectEvent -InputObject $KenticoPsAppDomain -EventName AssemblyResolve -SourceIdentifier AppDomainAssemblyResolve -Action $AssemblyResolveSb
[CMS.Base.SystemContext]::WebApplicationPhysicalPath = $CmsPath
"Application initialized:" + [CMS.DataEngine.CMSApplication]::Init();
[CMS.UIControls.Internal.DeploymentManager] $DeploymentManager = [KenticoCompilationMarshallable]::CreateDeploymentManager("bin\", "CMS.UIControls.dll")
try
{
[System.AppDomain]::CurrentDomain.SetDynamicBase("C:\inetpub\Kt90LatestDn45Src1\CMS\")
#$DeploymentManager = New-Object CMS.UIControls.Internal.DeploymentManager
$DeploymentManager.CompileVirtualObjects($null)
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Red
}
[System.AppDomain]::Unload($AppDomain)
}
$PathsArrayList = New-Object System.Collections.ArrayList
foreach ($Path in $Paths)
{
$PathsArrayList.Add($Path)
$PathSplit = Build-MetabasePath($Path)
Write-Host ""
Write-Host ("Starting a precompilation job for the IIS metabase path: LM/W3SVC/" + $PathSplit) -ForegroundColor Cyan
Write-Host ""
$PhysicalPath = $Path -split ";" | Select-Object -First 1
[ScriptBlock]$ScriptBlock = [ScriptBlock]::Create($ScriptBlockBase + $PathSplit + "; CompileVirtualObjects(" + $PhysicalPath + ")")
Start-Job -ScriptBlock $ScriptBlock -Name ($JobNamePrefix + $PathSplit.Replace("/", "-"))
}
Start-Sleep -Seconds 5
while (Get-Job | ? {$_.Name.Contains($JobNamePrefix)})
{
Get-Job | ? {$_.Name.Contains($JobNamePrefix) -and $_.State -eq "Completed"} | Remove-Job
if (Get-Job | ? {$_.Name.Contains($JobNamePrefix) -and ($_.State -eq "NotStarted" -or $_.State -eq "Running") -or $_.State -eq "Completed"})
{
Start-Sleep -Seconds 1
continue
}
elseif (Get-Job | ? {$_.Name.Contains($JobNamePrefix) -and $_.State -ne "NotStarted" -and $_.State -ne "Running" -and $_.State -ne "Completed"})
{
for ($X = $PathsArrayList.Count - 1; $X -ge 0; $X--)
{
$PathSplit = Build-MetabasePath($PathsArrayList[$X])
$JobName = ($JobNamePrefix + $PathSplit.Replace("/", "-"))
Write-Host ""
Write-Host ("Collecting the output of a precompilation job" + $JobName + " (IIS metabase path: LM/W3SVC/" + $PathSplit + "):") -ForegroundColor Gray
Receive-Job -Name $JobName
$CurrentJob = Get-Job -Name $JobName
if ($CurrentJob.State -ne "Completed")
{
Write-Host ""
Write-Host "The job" $JobName "hasn't probably finished correctly. Do you wish to delete the job?" -ForegroundColor Yellow
$Answer = Read-Host "If not, you can restart the job using the Start-Job cmdlet, press 'n' and hit Enter. If you wish to delete it, press 'y' and hit Enter"
if ($Answer -eq "y")
{
Remove-Job -Name $JobName
$PathsArrayList.Remove($PathsArrayList[$X])
}
else
{
Write-Host "You've chosen not to delete the" $JobName "job. If the job doesn't finish correctly, you will be able to remove it once it fails or using the Remove-Job cmdlet." -ForegroundColor Gray
}
}
else
{
Remove-Job -Name $JobName
}
}
}
else
{
# Handling undesired infinite looping here
break
}
}
@fmiopensource
Copy link

fmiopensource commented Oct 5, 2016

Getting an error while trying to run the script against our site, seems to be happening on the admin user verification. Maybe we are missing something? The paths variable has been updated to point at the site and we are running it as admin, it didn't seem like there was anything else to do from the instructions. The error output from PS is below:

E:>powershell -file E:\Precompile-KenticoInstances.ps1
At E:\Precompile-KenticoInstances.ps1:14 char:76

  • A  A  $windowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent()
  •                                                                        ~
    
    An expression was expected after '('.
    At E:\Precompile-KenticoInstances.ps1:35 char:28
  • A  A  [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
  •                        ~
    
    Missing argument in parameter list.
    At E:\Precompile-KenticoInstances.ps1:35 char:72
  • A  A  [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
  •                                                                    ~
    
    Missing closing ')' in expression.
    At E:\Precompile-KenticoInstances.ps1:33 char:1
  • {
  • ~
    Missing closing '}' in statement block.
    At E:\Precompile-KenticoInstances.ps1:37 char:12
  • A  A  $Path)
  •        ~
    
    Unexpected token ')' in expression or statement.
    At E:\Precompile-KenticoInstances.ps1:51 char:1
  • }
  • ~
    Unexpected token '}' in expression or statement.
    At E:\Precompile-KenticoInstances.ps1:57 char:28
  • A  A  [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
  •                        ~
    
    Missing argument in parameter list.
    At E:\Precompile-KenticoInstances.ps1:57 char:72
  • A  A  [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
  •                                                                    ~
    
    Missing closing ')' in expression.
    At E:\Precompile-KenticoInstances.ps1:55 char:1
  • {
  • ~
    Missing closing '}' in statement block.
    At E:\Precompile-KenticoInstances.ps1:59 char:20
  • A  A  $MyInvocation)
  •                ~
    
    Unexpected token ')' in expression or statement.
    Not all parse errors were reported. Correct the reported errors and try again.
    • CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
      ception
    • FullyQualifiedErrorId : ExpectedExpression

We are running Server 2012 R2.

Thanks for the help.

UPDATE:

Fixed the issue - Â apparently was being picked up in the script somehow although it was not visible in any text editor (only in the powershell editor)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment