Created
September 26, 2016 11:39
-
-
Save JanLenoch/2efa133553319b1c10bf56a804b89dd9 to your computer and use it in GitHub Desktop.
Get Near-Zero Load Time in Both Development and Production (Advanced)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
At E:\Precompile-KenticoInstances.ps1:35 char:28
At E:\Precompile-KenticoInstances.ps1:35 char:72
At E:\Precompile-KenticoInstances.ps1:33 char:1
Missing closing '}' in statement block.
At E:\Precompile-KenticoInstances.ps1:37 char:12
At E:\Precompile-KenticoInstances.ps1:51 char:1
Unexpected token '}' in expression or statement.
At E:\Precompile-KenticoInstances.ps1:57 char:28
At E:\Precompile-KenticoInstances.ps1:57 char:72
At E:\Precompile-KenticoInstances.ps1:55 char:1
Missing closing '}' in statement block.
At E:\Precompile-KenticoInstances.ps1:59 char:20
Not all parse errors were reported. Correct the reported errors and try again.
ception
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)