AI Ön İzleme
Bu içerik sana uygun mu? Kısa ön izleme ile hızlıca karar ver.
Techaptic’e hoş geldiniz!
Bugünkü özel çözüm rehberimizde, sorunlu personel çıkışlarında veya cihazın güvenlik riski oluşturduğu acil durumlarda şirket verilerini hızla güvence altına almayı ele alıyoruz.
Standart personel çıkış süreçleri belli olsa da acil durumlar anında aksiyon almayı gerektirir. ManageEngine Endpoint Central üzerinden özel bir acil durum bilgisayar izolasyon betiğinin (lockdown script) nasıl yapılandırılıp dağıtılacağını uygulamalı olarak gösteriyoruz. Bu rehberde, belirli kullanıcı hesaplarını hedeflemekten LAPS yapılandırmalarını koruyan “Zero Trust” (Sıfır Güven) tam kısıtlamalarına kadar dört farklı izolasyon senaryosunu kapsıyoruz. Erişimi nasıl hızla keseceğinizi, sorun giderme için detaylı JSON loglarını nasıl analiz edeceğinizi ve cihaz güvene alındığında erişimi nasıl kolayca geri yükleyeceğinizi adım adım takip edin.
🔗 Faydalı Linkler
- Bu bölümde paylaşılan bir link bulunmamaktadır.
Video Anlatımı
Kullanılan Komutlar
Acil bilgisayar kilitleme betiği
<#
.SYNOPSIS
Techaptic - Endpoint Central Emergency Lockdown Script (V1.4.0)
.DESCRIPTION
This Techaptic script applies reversible emergency access restrictions to a Windows endpoint
through ManageEngine Endpoint Central. It is designed to run as SYSTEM and supports four
operator scenarios: one specified account, multiple specified accounts, all human accounts,
or all human accounts except one strictly verified Windows LAPS recovery account.
The script stores rollback data and detailed verification evidence in the protected local state
file. Endpoint Central Remarks contains only the compact operational result.
.PARAMETER TargetUsers
Required target definition.
Supported examples:
"DOMAIN\user1"
"DOMAIN\user1;COMPUTER\localuser"
"ALL"
.PARAMETER SaveLaps
Valid only with -TargetUsers "ALL". Preserves one verified Windows LAPS local administrator as
the recovery identity. If the LAPS identity cannot be safely verified, the script stops before
applying security changes.
.EXAMPLE
.\Techaptic-EPC-EmergencyLockdown-v1.4.0.ps1 -TargetUsers "DOMAIN\user1"
.EXAMPLE
.\Techaptic-EPC-EmergencyLockdown-v1.4.0.ps1 -TargetUsers "DOMAIN\user1;COMPUTER\localuser"
.EXAMPLE
.\Techaptic-EPC-EmergencyLockdown-v1.4.0.ps1 -TargetUsers "ALL"
.EXAMPLE
.\Techaptic-EPC-EmergencyLockdown-v1.4.0.ps1 -TargetUsers "ALL" -SaveLaps
.NOTES
Author : Techaptic
Maintainer : Techaptic
Script : Endpoint Central Emergency Lockdown
Version : 1.4.0
Execution : Endpoint Central Computer Configuration / Run as SYSTEM
State File : C:\ProgramData\EmergencyLockdown\EmergencyLockdown.json
IMPORTANT SAFETY NOTES:
- This is a disruptive emergency-control script. Depending on scope, it can block console,
RDP, network, cached-domain, and online-domain access and will initiate sign-out/restart.
- Validate the complete Lockdown and Restore cycle in a representative test environment before
any production use.
- Keep the matching Techaptic Restore script available and confirm Endpoint Central agent
delivery before applying Lockdown.
- "ALL" without -SaveLaps may leave the Endpoint Central agent as the only planned remote
recovery path.
- "ALL" with -SaveLaps must be used only after the managed LAPS account and its current password
have been independently verified.
- Do not delete, rename, or manually edit the protected state file while Lockdown is active.
- Do not modify embedded Base64 components, internal task names, firewall identifiers, state
paths, or rollback logic unless the complete test matrix is repeated.
- Customer change approval, backup, rollback, access-control, and production validation
procedures remain mandatory.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[AllowEmptyString()]
[string]$TargetUsers,
[Parameter(Mandatory = $false)]
[switch]$SaveLaps
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$ScriptVersion = '1.4.0'
$BaseDirectory = Join-Path $env:ProgramData 'EmergencyLockdown'
$StatePath = Join-Path $BaseDirectory 'EmergencyLockdown.json'
$TemporaryStatePath = Join-Path $BaseDirectory 'EmergencyLockdown.tmp'
$FirewallRuleGroup = 'EmergencyLockdown'
$FirewallRulePrefix = 'EmergencyLockdown-ADAuth'
$RebootDelaySeconds = 60
$PostBootVerifierPath = Join-Path $BaseDirectory 'EmergencyPostBootVerify.ps1'
$PostBootTaskName = 'EmergencyLockdown-PostBootVerify-v140'
$DisruptionWorkerPath = Join-Path $BaseDirectory 'EmergencyDisruptionWorker.ps1'
$DisruptionTaskName = 'EmergencyLockdown-DisruptionWorker-v140'
$PostBootVerifierBase64 = 'PCMKLlNZTk9QU0lTCiAgICBBdXRvbWF0aWMgcG9zdC1ib290IHZlcmlmaWNhdGlvbiBjb21wb25lbnQgZm9yIEVtZXJnZW5jeSBMb2NrZG93biBWMS40LjAuCi5ERVNDUklQVElPTgogICAgR2VuZXJhdGVkIGFuZCByZWdpc3RlcmVkIGludGVybmFsbHkgYnkgdGhlIExvY2tkb3duL1Jlc3RvcmUgc2NyaXB0cy4KICAgIEl0IHJ1bnMgYXMgU1lTVEVNIGFmdGVyIHJlYm9vdCwgdmVyaWZpZXMgZWZmZWN0aXZlIHN0YXRlLCB1cGRhdGVzIHRoZSBBQ0wtcHJvdGVjdGVkIEpTT04sIGFuZCByZW1vdmVzIGl0cyBvd24gdGFzay4KICAgIEl0IGlzIG5vdCBhbiBvcGVyYXRvci1mYWNpbmcgRW5kcG9pbnQgQ2VudHJhbCBzY3JpcHQuCiM+CnBhcmFtKCkKU2V0LVN0cmljdE1vZGUgLVZlcnNpb24gMi4wCiRFcnJvckFjdGlvblByZWZlcmVuY2UgPSAnU3RvcCcKJFByb2dyZXNzUHJlZmVyZW5jZSA9ICdTaWxlbnRseUNvbnRpbnVlJwoKJEJhc2VEaXJlY3RvcnkgPSBKb2luLVBhdGggJGVudjpQcm9ncmFtRGF0YSAnRW1lcmdlbmN5TG9ja2Rvd24nCiRTdGF0ZVBhdGggPSBKb2luLVBhdGggJEJhc2VEaXJlY3RvcnkgJ0VtZXJnZW5jeUxvY2tkb3duLmpzb24nCiRUZW1wb3JhcnlTdGF0ZVBhdGggPSBKb2luLVBhdGggJEJhc2VEaXJlY3RvcnkgJ0VtZXJnZW5jeUxvY2tkb3duLnRtcCcKJFRhc2tOYW1lID0gJ0VtZXJnZW5jeUxvY2tkb3duLVBvc3RCb290VmVyaWZ5LXYxNDAnCiRzY3JpcHQ6VmVyaWZpY2F0aW9uUmVjb3JkZWQgPSAkZmFsc2UKU3RhcnQtU2xlZXAgLVNlY29uZHMgNjAKCmZ1bmN0aW9uIEdldC1VdGNUaW1lc3RhbXAgeyBbRGF0ZVRpbWVdOjpVdGNOb3cuVG9TdHJpbmcoJ28nKSB9CmZ1bmN0aW9uIEdldC1Qcm9wZXJ0eVZhbHVlIHsKICAgIHBhcmFtKCRPYmplY3QsW3N0cmluZ10kTmFtZSwkRGVmYXVsdD0kbnVsbCkKICAgIGlmICgkbnVsbCAtZXEgJE9iamVjdCkgeyByZXR1cm4gJERlZmF1bHQgfQogICAgJHByb3BlcnR5ID0gJE9iamVjdC5QU09iamVjdC5Qcm9wZXJ0aWVzWyROYW1lXQogICAgaWYgKCRudWxsIC1lcSAkcHJvcGVydHkpIHsgcmV0dXJuICREZWZhdWx0IH0KICAgIHJldHVybiAkcHJvcGVydHkuVmFsdWUKfQoKZnVuY3Rpb24gSW5pdGlhbGl6ZS1XdHNOYXRpdmVUeXBlIHsKICAgIGlmICgnRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3RXdHNWMTQwJyAtYXMgW3R5cGVdKSB7IHJldHVybiB9CiAgICAkc291cmNlID0gQCcKdXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkNvbXBvbmVudE1vZGVsOwp1c2luZyBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXM7Cm5hbWVzcGFjZSBFbWVyZ2VuY3lTZWN1cml0eQp7CiAgICBwdWJsaWMgc3RhdGljIGNsYXNzIFBvc3RCb290V3RzVjE0MAogICAgewogICAgICAgIFtTdHJ1Y3RMYXlvdXQoTGF5b3V0S2luZC5TZXF1ZW50aWFsLCBDaGFyU2V0PUNoYXJTZXQuVW5pY29kZSldCiAgICAgICAgcHJpdmF0ZSBzdHJ1Y3QgV1RTX1NFU1NJT05fSU5GTyB7IHB1YmxpYyBpbnQgU2Vzc2lvbklEOyBwdWJsaWMgc3RyaW5nIHBXaW5TdGF0aW9uTmFtZTsgcHVibGljIGludCBTdGF0ZTsgfQogICAgICAgIHByaXZhdGUgZW51bSBXVFNfSU5GT19DTEFTUyB7IFdUU0luaXRpYWxQcm9ncmFtLCBXVFNBcHBsaWNhdGlvbk5hbWUsIFdUU1dvcmtpbmdEaXJlY3RvcnksIFdUU09FTUlkLCBXVFNTZXNzaW9uSWQsIFdUU1VzZXJOYW1lLCBXVFNXaW5TdGF0aW9uTmFtZSwgV1RTRG9tYWluTmFtZSB9CiAgICAgICAgW0RsbEltcG9ydCgid3RzYXBpMzIuZGxsIiwgU2V0TGFzdEVycm9yPXRydWUpXSBwcml2YXRlIHN0YXRpYyBleHRlcm4gYm9vbCBXVFNFbnVtZXJhdGVTZXNzaW9ucyhJbnRQdHIgaFNlcnZlciwgaW50IFJlc2VydmVkLCBpbnQgVmVyc2lvbiwgb3V0IEludFB0ciBwcFNlc3Npb25JbmZvLCBvdXQgaW50IHBDb3VudCk7CiAgICAgICAgW0RsbEltcG9ydCgid3RzYXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB2b2lkIFdUU0ZyZWVNZW1vcnkoSW50UHRyIHBNZW1vcnkpOwogICAgICAgIFtEbGxJbXBvcnQoInd0c2FwaTMyLmRsbCIsIENoYXJTZXQ9Q2hhclNldC5Vbmljb2RlLCBTZXRMYXN0RXJyb3I9dHJ1ZSldIHByaXZhdGUgc3RhdGljIGV4dGVybiBib29sIFdUU1F1ZXJ5U2Vzc2lvbkluZm9ybWF0aW9uKEludFB0ciBoU2VydmVyLCBpbnQgc2Vzc2lvbklkLCBXVFNfSU5GT19DTEFTUyBpbmZvQ2xhc3MsIG91dCBJbnRQdHIgcHBCdWZmZXIsIG91dCBpbnQgcEJ5dGVzUmV0dXJuZWQpOwogICAgICAgIHByaXZhdGUgc3RhdGljIHN0cmluZyBRdWVyeShpbnQgc2Vzc2lvbklkLCBXVFNfSU5GT19DTEFTUyBpbmZvQ2xhc3MpIHsgSW50UHRyIHA9SW50UHRyLlplcm87IGludCBieXRlcz0wOyB0cnkgeyBpZighV1RTUXVlcnlTZXNzaW9uSW5mb3JtYXRpb24oSW50UHRyLlplcm8sc2Vzc2lvbklkLGluZm9DbGFzcyxvdXQgcCxvdXQgYnl0ZXMpKSByZXR1cm4gIiI7IHJldHVybiBwPT1JbnRQdHIuWmVybyA/ICIiIDogTWFyc2hhbC5QdHJUb1N0cmluZ1VuaShwKTsgfSBmaW5hbGx5IHsgaWYocCE9SW50UHRyLlplcm8pIFdUU0ZyZWVNZW1vcnkocCk7IH0gfQogICAgICAgIHB1YmxpYyBzdGF0aWMgc3RyaW5nW10gR2V0U2Vzc2lvbnMoKSB7IEludFB0ciBidWZmZXI9SW50UHRyLlplcm87IGludCBjb3VudD0wOyBMaXN0PHN0cmluZz4gcmVzdWx0PW5ldyBMaXN0PHN0cmluZz4oKTsgdHJ5IHsgaWYoIVdUU0VudW1lcmF0ZVNlc3Npb25zKEludFB0ci5aZXJvLDAsMSxvdXQgYnVmZmVyLG91dCBjb3VudCkpIHRocm93IG5ldyBXaW4zMkV4Y2VwdGlvbihNYXJzaGFsLkdldExhc3RXaW4zMkVycm9yKCkpOyBpbnQgc2l6ZT1NYXJzaGFsLlNpemVPZih0eXBlb2YoV1RTX1NFU1NJT05fSU5GTykpOyBmb3IoaW50IGk9MDtpPGNvdW50O2krKyl7IFdUU19TRVNTSU9OX0lORk8gaXRlbT0oV1RTX1NFU1NJT05fSU5GTylNYXJzaGFsLlB0clRvU3RydWN0dXJlKEludFB0ci5BZGQoYnVmZmVyLGkqc2l6ZSksdHlwZW9mKFdUU19TRVNTSU9OX0lORk8pKTsgc3RyaW5nIHVzZXI9UXVlcnkoaXRlbS5TZXNzaW9uSUQsV1RTX0lORk9fQ0xBU1MuV1RTVXNlck5hbWUpOyBzdHJpbmcgZG9tYWluPVF1ZXJ5KGl0ZW0uU2Vzc2lvbklELFdUU19JTkZPX0NMQVNTLldUU0RvbWFpbk5hbWUpOyByZXN1bHQuQWRkKGl0ZW0uU2Vzc2lvbklEKyJ8Iitkb21haW4rInwiK3VzZXIrInwiK2l0ZW0uU3RhdGUpOyB9IHJldHVybiByZXN1bHQuVG9BcnJheSgpOyB9IGZpbmFsbHkgeyBpZihidWZmZXIhPUludFB0ci5aZXJvKSBXVFNGcmVlTWVtb3J5KGJ1ZmZlcik7IH0gfQogICAgfQp9CidACiAgICBBZGQtVHlwZSAtVHlwZURlZmluaXRpb24gJHNvdXJjZSAtTGFuZ3VhZ2UgQ1NoYXJwIC1FcnJvckFjdGlvbiBTdG9wIHwgT3V0LU51bGwKfQpmdW5jdGlvbiBHZXQtV3RzU2Vzc2lvbnMgewogICAgJGl0ZW1zID0gQCgpCiAgICBmb3JlYWNoICgkbGluZSBpbiBbRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3RXdHNWMTQwXTo6R2V0U2Vzc2lvbnMoKSkgewogICAgICAgICRwYXJ0cyA9ICRsaW5lLlNwbGl0KCd8JykKICAgICAgICBpZiAoJHBhcnRzLkNvdW50IC1sdCA0KSB7IGNvbnRpbnVlIH0KICAgICAgICAkaWQgPSBbaW50XSRwYXJ0c1swXQogICAgICAgICRkb21haW4gPSBbc3RyaW5nXSRwYXJ0c1sxXQogICAgICAgICR1c2VyID0gW3N0cmluZ10kcGFydHNbMl0KICAgICAgICBpZiAoJGlkIC1sZSAwIC1vciBbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCR1c2VyKSkgeyBjb250aW51ZSB9CiAgICAgICAgJGFjY291bnQgPSBpZiAoW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkZG9tYWluKSkgeyAkdXNlciB9IGVsc2UgeyAiJGRvbWFpblwkdXNlciIgfQogICAgICAgICRpdGVtcyArPSBbcHNjdXN0b21vYmplY3RdQHtzZXNzaW9uSWQ9JGlkO2FjY291bnROYW1lPSRhY2NvdW50O3N0YXRlPVtpbnRdJHBhcnRzWzNdfQogICAgfQogICAgcmV0dXJuICRpdGVtcwp9CmZ1bmN0aW9uIE5vcm1hbGl6ZS1TZXNzaW9uQWNjb3VudE5hbWUgewogICAgcGFyYW0oW3N0cmluZ10kTmFtZSkKICAgIGlmIChbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCROYW1lKSkgeyByZXR1cm4gJycgfQogICAgJHZhbHVlPSROYW1lLlRyaW0oKQogICAgaWYgKCR2YWx1ZS5TdGFydHNXaXRoKCcuXCcpKSB7IHJldHVybiAiJGVudjpDT01QVVRFUk5BTUVcJCgkdmFsdWUuU3Vic3RyaW5nKDIpKSIuVG9Mb3dlckludmFyaWFudCgpIH0KICAgIHJldHVybiAkdmFsdWUuVG9Mb3dlckludmFyaWFudCgpCn0KZnVuY3Rpb24gU2V0LVNlY3VyZURpcmVjdG9yeUFjbCB7CiAgICBwYXJhbShbc3RyaW5nXSRQYXRoKQogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFBhdGgpKSB7ICRudWxsID0gTmV3LUl0ZW0gLVBhdGggJFBhdGggLUl0ZW1UeXBlIERpcmVjdG9yeSAtRm9yY2UgfQogICAgJHN5c3RlbVNpZCA9IFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLlNlY3VyaXR5SWRlbnRpZmllcl06Om5ldygnUy0xLTUtMTgnKQogICAgJGFkbWluaXN0cmF0b3JzU2lkID0gW1N5c3RlbS5TZWN1cml0eS5QcmluY2lwYWwuU2VjdXJpdHlJZGVudGlmaWVyXTo6bmV3KCdTLTEtNS0zMi01NDQnKQogICAgJGluaGVyaXRhbmNlID0gW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkluaGVyaXRhbmNlRmxhZ3NdOjpDb250YWluZXJJbmhlcml0IC1ib3IgW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkluaGVyaXRhbmNlRmxhZ3NdOjpPYmplY3RJbmhlcml0CiAgICAkc2VjdXJpdHkgPSBbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRGlyZWN0b3J5U2VjdXJpdHldOjpuZXcoKQogICAgJHNlY3VyaXR5LlNldEFjY2Vzc1J1bGVQcm90ZWN0aW9uKCR0cnVlLCRmYWxzZSkKICAgICRzZWN1cml0eS5TZXRPd25lcigkc3lzdGVtU2lkKQogICAgJHNlY3VyaXR5LkFkZEFjY2Vzc1J1bGUoW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1BY2Nlc3NSdWxlXTo6bmV3KCRzeXN0ZW1TaWQsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1SaWdodHNdOjpGdWxsQ29udHJvbCwkaW5oZXJpdGFuY2UsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLlByb3BhZ2F0aW9uRmxhZ3NdOjpOb25lLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgICRzZWN1cml0eS5BZGRBY2Nlc3NSdWxlKFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5GaWxlU3lzdGVtQWNjZXNzUnVsZV06Om5ldygkYWRtaW5pc3RyYXRvcnNTaWQsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1SaWdodHNdOjpGdWxsQ29udHJvbCwkaW5oZXJpdGFuY2UsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLlByb3BhZ2F0aW9uRmxhZ3NdOjpOb25lLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgIFtTeXN0ZW0uSU8uRGlyZWN0b3J5SW5mb106Om5ldygkUGF0aCkuU2V0QWNjZXNzQ29udHJvbCgkc2VjdXJpdHkpCn0KZnVuY3Rpb24gU2V0LVNlY3VyZUZpbGVBY2wgewogICAgcGFyYW0oW3N0cmluZ10kUGF0aCkKICAgIGlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRQYXRoKSkgeyByZXR1cm4gfQogICAgJHN5c3RlbVNpZCA9IFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLlNlY3VyaXR5SWRlbnRpZmllcl06Om5ldygnUy0xLTUtMTgnKQogICAgJGFkbWluaXN0cmF0b3JzU2lkID0gW1N5c3RlbS5TZWN1cml0eS5QcmluY2lwYWwuU2VjdXJpdHlJZGVudGlmaWVyXTo6bmV3KCdTLTEtNS0zMi01NDQnKQogICAgJHNlY3VyaXR5ID0gW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTZWN1cml0eV06Om5ldygpCiAgICAkc2VjdXJpdHkuU2V0QWNjZXNzUnVsZVByb3RlY3Rpb24oJHRydWUsJGZhbHNlKQogICAgJHNlY3VyaXR5LlNldE93bmVyKCRzeXN0ZW1TaWQpCiAgICAkc2VjdXJpdHkuQWRkQWNjZXNzUnVsZShbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbUFjY2Vzc1J1bGVdOjpuZXcoJHN5c3RlbVNpZCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbVJpZ2h0c106OkZ1bGxDb250cm9sLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgICRzZWN1cml0eS5BZGRBY2Nlc3NSdWxlKFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5GaWxlU3lzdGVtQWNjZXNzUnVsZV06Om5ldygkYWRtaW5pc3RyYXRvcnNTaWQsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1SaWdodHNdOjpGdWxsQ29udHJvbCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuQWNjZXNzQ29udHJvbFR5cGVdOjpBbGxvdykpCiAgICBbU3lzdGVtLklPLkZpbGVJbmZvXTo6bmV3KCRQYXRoKS5TZXRBY2Nlc3NDb250cm9sKCRzZWN1cml0eSkKfQpmdW5jdGlvbiBXcml0ZS1TdGF0ZSB7CiAgICBwYXJhbSgkU3RhdGUpCiAgICBTZXQtU2VjdXJlRGlyZWN0b3J5QWNsIC1QYXRoICRCYXNlRGlyZWN0b3J5CiAgICAkanNvbiA9ICRTdGF0ZSB8IENvbnZlcnRUby1Kc29uIC1EZXB0aCAyNAogICAgW1N5c3RlbS5JTy5GaWxlXTo6V3JpdGVBbGxUZXh0KCRUZW1wb3JhcnlTdGF0ZVBhdGgsJGpzb24sW1N5c3RlbS5UZXh0LlVURjhFbmNvZGluZ106Om5ldygkZmFsc2UpKQogICAgU2V0LVNlY3VyZUZpbGVBY2wgLVBhdGggJFRlbXBvcmFyeVN0YXRlUGF0aAogICAgTW92ZS1JdGVtIC1MaXRlcmFsUGF0aCAkVGVtcG9yYXJ5U3RhdGVQYXRoIC1EZXN0aW5hdGlvbiAkU3RhdGVQYXRoIC1Gb3JjZQogICAgU2V0LVNlY3VyZUZpbGVBY2wgLVBhdGggJFN0YXRlUGF0aAp9CmZ1bmN0aW9uIE5ldy1DaGVjayB7CiAgICBwYXJhbShbc3RyaW5nXSROYW1lLFtWYWxpZGF0ZVNldCgnUEFTUycsJ0ZBSUwnLCdXQVJOSU5HJywnTk9UX0FQUExJQ0FCTEUnKV1bc3RyaW5nXSRTdGF0dXMsW3N0cmluZ10kRXhwZWN0ZWQsW3N0cmluZ10kQWN0dWFsLFtzdHJpbmddJE1lc3NhZ2U9JG51bGwpCiAgICBbcHNjdXN0b21vYmplY3RdW29yZGVyZWRdQHtuYW1lPSROYW1lO3N0YXR1cz0kU3RhdHVzO2V4cGVjdGVkPSRFeHBlY3RlZDthY3R1YWw9JEFjdHVhbDttZXNzYWdlPSRNZXNzYWdlfQp9CmZ1bmN0aW9uIFRlc3QtU2VjdXJlQWNsIHsKICAgIHBhcmFtKFtzdHJpbmddJFBhdGgpCiAgICB0cnkgewogICAgICAgICRhY2wgPSBHZXQtQWNsIC1MaXRlcmFsUGF0aCAkUGF0aCAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgICRvd25lclNpZCA9IHRyeSB7IChbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5OVEFjY291bnRdJGFjbC5Pd25lcikuVHJhbnNsYXRlKFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLlNlY3VyaXR5SWRlbnRpZmllcl0pLlZhbHVlIH0gY2F0Y2ggeyBbc3RyaW5nXSRhY2wuT3duZXIgfQogICAgICAgICRhbGxvd2VkID0gQCgnUy0xLTUtMTgnLCdTLTEtNS0zMi01NDQnKQogICAgICAgICR1bmV4cGVjdGVkID0gQCgpCiAgICAgICAgZm9yZWFjaCAoJHJ1bGUgaW4gQCgkYWNsLkFjY2VzcykpIHsKICAgICAgICAgICAgJHNpZCA9IHRyeSB7ICRydWxlLklkZW50aXR5UmVmZXJlbmNlLlRyYW5zbGF0ZShbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5TZWN1cml0eUlkZW50aWZpZXJdKS5WYWx1ZSB9IGNhdGNoIHsgW3N0cmluZ10kcnVsZS5JZGVudGl0eVJlZmVyZW5jZSB9CiAgICAgICAgICAgIGlmICgkcnVsZS5BY2Nlc3NDb250cm9sVHlwZSAtbmUgJ0FsbG93JyAtb3IgJHNpZCAtbm90aW4gJGFsbG93ZWQpIHsgJHVuZXhwZWN0ZWQgKz0gJHNpZCB9CiAgICAgICAgfQogICAgICAgIHJldHVybiBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSgkYWNsLkFyZUFjY2Vzc1J1bGVzUHJvdGVjdGVkIC1hbmQgJG93bmVyU2lkIC1lcSAnUy0xLTUtMTgnIC1hbmQgJHVuZXhwZWN0ZWQuQ291bnQgLWVxIDApO093bmVyPSRvd25lclNpZDtQcm90ZWN0ZWQ9W2Jvb2xdJGFjbC5BcmVBY2Nlc3NSdWxlc1Byb3RlY3RlZDtVbmV4cGVjdGVkPSgkdW5leHBlY3RlZCAtam9pbiAnOycpfQogICAgfSBjYXRjaCB7IHJldHVybiBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSRmYWxzZTtPd25lcj0nVU5LTk9XTic7UHJvdGVjdGVkPSRmYWxzZTtVbmV4cGVjdGVkPSRfLkV4Y2VwdGlvbi5NZXNzYWdlfSB9Cn0KZnVuY3Rpb24gSW5pdGlhbGl6ZS1Mc2FOYXRpdmVUeXBlIHsKICAgIGlmICgnRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3ROYXRpdmVWMTQwJyAtYXMgW3R5cGVdKSB7IHJldHVybiB9CiAgICAkc291cmNlID0gQCcKdXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkNvbXBvbmVudE1vZGVsOwp1c2luZyBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXM7CnVzaW5nIFN5c3RlbS5TZWN1cml0eS5QcmluY2lwYWw7Cm5hbWVzcGFjZSBFbWVyZ2VuY3lTZWN1cml0eQp7CiAgICBwdWJsaWMgc3RhdGljIGNsYXNzIFBvc3RCb290TmF0aXZlVjE0MAogICAgewogICAgICAgIFtTdHJ1Y3RMYXlvdXQoTGF5b3V0S2luZC5TZXF1ZW50aWFsKV0gcHJpdmF0ZSBzdHJ1Y3QgTFNBX09CSkVDVF9BVFRSSUJVVEVTIHsgcHVibGljIGludCBMZW5ndGg7IHB1YmxpYyBJbnRQdHIgUm9vdERpcmVjdG9yeTsgcHVibGljIEludFB0ciBPYmplY3ROYW1lOyBwdWJsaWMgdWludCBBdHRyaWJ1dGVzOyBwdWJsaWMgSW50UHRyIFNlY3VyaXR5RGVzY3JpcHRvcjsgcHVibGljIEludFB0ciBTZWN1cml0eVF1YWxpdHlPZlNlcnZpY2U7IH0KICAgICAgICBbU3RydWN0TGF5b3V0KExheW91dEtpbmQuU2VxdWVudGlhbCldIHByaXZhdGUgc3RydWN0IExTQV9VTklDT0RFX1NUUklORyB7IHB1YmxpYyB1c2hvcnQgTGVuZ3RoOyBwdWJsaWMgdXNob3J0IE1heGltdW1MZW5ndGg7IHB1YmxpYyBJbnRQdHIgQnVmZmVyOyB9CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYU9wZW5Qb2xpY3koSW50UHRyIFN5c3RlbU5hbWUsIHJlZiBMU0FfT0JKRUNUX0FUVFJJQlVURVMgT2JqZWN0QXR0cmlidXRlcywgdWludCBEZXNpcmVkQWNjZXNzLCBvdXQgSW50UHRyIFBvbGljeUhhbmRsZSk7CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYUVudW1lcmF0ZUFjY291bnRSaWdodHMoSW50UHRyIFBvbGljeUhhbmRsZSwgSW50UHRyIEFjY291bnRTaWQsIG91dCBJbnRQdHIgVXNlclJpZ2h0cywgb3V0IHVpbnQgQ291bnRPZlJpZ2h0cyk7CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYU50U3RhdHVzVG9XaW5FcnJvcih1aW50IFN0YXR1cyk7CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYUNsb3NlKEludFB0ciBQb2xpY3lIYW5kbGUpOwogICAgICAgIFtEbGxJbXBvcnQoImFkdmFwaTMyLmRsbCIpXSBwcml2YXRlIHN0YXRpYyBleHRlcm4gdWludCBMc2FGcmVlTWVtb3J5KEludFB0ciBCdWZmZXIpOwogICAgICAgIFtEbGxJbXBvcnQoIk5ldGFwaTMyLmRsbCIsIENoYXJTZXQ9Q2hhclNldC5Vbmljb2RlKV0gcHJpdmF0ZSBzdGF0aWMgZXh0ZXJuIGludCBOZXRMb2NhbEdyb3VwR2V0TWVtYmVycyhzdHJpbmcgc2VydmVybmFtZSwgc3RyaW5nIGxvY2FsZ3JvdXBuYW1lLCBpbnQgbGV2ZWwsIG91dCBJbnRQdHIgYnVmcHRyLCBpbnQgcHJlZm1heGxlbiwgb3V0IGludCBlbnRyaWVzcmVhZCwgb3V0IGludCB0b3RhbGVudHJpZXMsIHJlZiBJbnRQdHIgcmVzdW1lX2hhbmRsZSk7CiAgICAgICAgW0RsbEltcG9ydCgiTmV0YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiBpbnQgTmV0QXBpQnVmZmVyRnJlZShJbnRQdHIgQnVmZmVyKTsKICAgICAgICBwcml2YXRlIGNvbnN0IHVpbnQgUE9MSUNZX0xPT0tVUF9OQU1FUz0weDAwMDAwODAwOwogICAgICAgIHByaXZhdGUgY29uc3QgdWludCBTVEFUVVNfT0JKRUNUX05BTUVfTk9UX0ZPVU5EPTB4QzAwMDAwMzQ7CiAgICAgICAgcHJpdmF0ZSBjb25zdCBpbnQgTUFYX1BSRUZFUlJFRF9MRU5HVEg9LTE7CiAgICAgICAgcHJpdmF0ZSBjb25zdCBpbnQgRVJST1JfTU9SRV9EQVRBPTIzNDsKICAgICAgICBwcml2YXRlIHN0YXRpYyBJbnRQdHIgU2lkVG9QdHIoc3RyaW5nIHNpZFZhbHVlKXsgU2VjdXJpdHlJZGVudGlmaWVyIHNpZD1uZXcgU2VjdXJpdHlJZGVudGlmaWVyKHNpZFZhbHVlKTsgYnl0ZVtdIGI9bmV3IGJ5dGVbc2lkLkJpbmFyeUxlbmd0aF07IHNpZC5HZXRCaW5hcnlGb3JtKGIsMCk7IEludFB0ciBwPU1hcnNoYWwuQWxsb2NIR2xvYmFsKGIuTGVuZ3RoKTsgTWFyc2hhbC5Db3B5KGIsMCxwLGIuTGVuZ3RoKTsgcmV0dXJuIHA7IH0KICAgICAgICBwdWJsaWMgc3RhdGljIHN0cmluZ1tdIEdldFJpZ2h0cyhzdHJpbmcgc2lkVmFsdWUpCiAgICAgICAgewogICAgICAgICAgICBMU0FfT0JKRUNUX0FUVFJJQlVURVMgbG9hPW5ldyBMU0FfT0JKRUNUX0FUVFJJQlVURVMoKTsgbG9hLkxlbmd0aD1NYXJzaGFsLlNpemVPZih0eXBlb2YoTFNBX09CSkVDVF9BVFRSSUJVVEVTKSk7CiAgICAgICAgICAgIEludFB0ciBwb2xpY3k9SW50UHRyLlplcm87IEludFB0ciBzaWQ9SW50UHRyLlplcm87IEludFB0ciByaWdodHM9SW50UHRyLlplcm87CiAgICAgICAgICAgIHRyeQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICB1aW50IHN0YXR1cz1Mc2FPcGVuUG9saWN5KEludFB0ci5aZXJvLHJlZiBsb2EsUE9MSUNZX0xPT0tVUF9OQU1FUyxvdXQgcG9saWN5KTsKICAgICAgICAgICAgICAgIGlmKHN0YXR1cyE9MCkgdGhyb3cgbmV3IFdpbjMyRXhjZXB0aW9uKChpbnQpTHNhTnRTdGF0dXNUb1dpbkVycm9yKHN0YXR1cykpOwogICAgICAgICAgICAgICAgc2lkPVNpZFRvUHRyKHNpZFZhbHVlKTsgdWludCBjb3VudDsKICAgICAgICAgICAgICAgIHN0YXR1cz1Mc2FFbnVtZXJhdGVBY2NvdW50UmlnaHRzKHBvbGljeSxzaWQsb3V0IHJpZ2h0cyxvdXQgY291bnQpOwogICAgICAgICAgICAgICAgaWYoc3RhdHVzPT1TVEFUVVNfT0JKRUNUX05BTUVfTk9UX0ZPVU5EKSByZXR1cm4gbmV3IHN0cmluZ1swXTsKICAgICAgICAgICAgICAgIGlmKHN0YXR1cyE9MCkgdGhyb3cgbmV3IFdpbjMyRXhjZXB0aW9uKChpbnQpTHNhTnRTdGF0dXNUb1dpbkVycm9yKHN0YXR1cykpOwogICAgICAgICAgICAgICAgTGlzdDxzdHJpbmc+IGxpc3Q9bmV3IExpc3Q8c3RyaW5nPigpOyBpbnQgc2l6ZT1NYXJzaGFsLlNpemVPZih0eXBlb2YoTFNBX1VOSUNPREVfU1RSSU5HKSk7CiAgICAgICAgICAgICAgICBmb3IoaW50IGk9MDtpPGNvdW50O2krKykgeyBMU0FfVU5JQ09ERV9TVFJJTkcgaXRlbT0oTFNBX1VOSUNPREVfU1RSSU5HKU1hcnNoYWwuUHRyVG9TdHJ1Y3R1cmUoSW50UHRyLkFkZChyaWdodHMsaSpzaXplKSx0eXBlb2YoTFNBX1VOSUNPREVfU1RSSU5HKSk7IGxpc3QuQWRkKE1hcnNoYWwuUHRyVG9TdHJpbmdVbmkoaXRlbS5CdWZmZXIsaXRlbS5MZW5ndGgvMikpOyB9CiAgICAgICAgICAgICAgICByZXR1cm4gbGlzdC5Ub0FycmF5KCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZmluYWxseSB7IGlmKHJpZ2h0cyE9SW50UHRyLlplcm8pTHNhRnJlZU1lbW9yeShyaWdodHMpOyBpZihzaWQhPUludFB0ci5aZXJvKU1hcnNoYWwuRnJlZUhHbG9iYWwoc2lkKTsgaWYocG9saWN5IT1JbnRQdHIuWmVybylMc2FDbG9zZShwb2xpY3kpOyB9CiAgICAgICAgfQoKICAgICAgICBwdWJsaWMgc3RhdGljIHN0cmluZ1tdIEdldExvY2FsR3JvdXBNZW1iZXJTaWRzKHN0cmluZyBncm91cE5hbWUpCiAgICAgICAgewogICAgICAgICAgICBMaXN0PHN0cmluZz4gcmVzdWx0cz1uZXcgTGlzdDxzdHJpbmc+KCk7CiAgICAgICAgICAgIEludFB0ciByZXN1bWU9SW50UHRyLlplcm87CiAgICAgICAgICAgIGludCBzdGF0dXM7CiAgICAgICAgICAgIGRvCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIEludFB0ciBidWZmZXI9SW50UHRyLlplcm87CiAgICAgICAgICAgICAgICBpbnQgZW50cmllc1JlYWQ9MDsKICAgICAgICAgICAgICAgIGludCB0b3RhbEVudHJpZXM9MDsKICAgICAgICAgICAgICAgIHRyeQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHN0YXR1cz1OZXRMb2NhbEdyb3VwR2V0TWVtYmVycyhudWxsLGdyb3VwTmFtZSwwLG91dCBidWZmZXIsTUFYX1BSRUZFUlJFRF9MRU5HVEgsb3V0IGVudHJpZXNSZWFkLG91dCB0b3RhbEVudHJpZXMscmVmIHJlc3VtZSk7CiAgICAgICAgICAgICAgICAgICAgaWYoc3RhdHVzIT0wICYmIHN0YXR1cyE9RVJST1JfTU9SRV9EQVRBKSB0aHJvdyBuZXcgV2luMzJFeGNlcHRpb24oc3RhdHVzKTsKICAgICAgICAgICAgICAgICAgICBmb3IoaW50IGk9MDtpPGVudHJpZXNSZWFkO2krKykKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgIEludFB0ciBzaWRQdHI9TWFyc2hhbC5SZWFkSW50UHRyKGJ1ZmZlcixpKkludFB0ci5TaXplKTsKICAgICAgICAgICAgICAgICAgICAgICAgaWYoc2lkUHRyIT1JbnRQdHIuWmVybykgcmVzdWx0cy5BZGQobmV3IFNlY3VyaXR5SWRlbnRpZmllcihzaWRQdHIpLlZhbHVlKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBmaW5hbGx5IHsgaWYoYnVmZmVyIT1JbnRQdHIuWmVybylOZXRBcGlCdWZmZXJGcmVlKGJ1ZmZlcik7IH0KICAgICAgICAgICAgfSB3aGlsZShzdGF0dXM9PUVSUk9SX01PUkVfREFUQSk7CiAgICAgICAgICAgIHJldHVybiByZXN1bHRzLlRvQXJyYXkoKTsKICAgICAgICB9CiAgICB9Cn0KJ0AKICAgIEFkZC1UeXBlIC1UeXBlRGVmaW5pdGlvbiAkc291cmNlIC1MYW5ndWFnZSBDU2hhcnAgLUVycm9yQWN0aW9uIFN0b3AgfCBPdXQtTnVsbAp9CmZ1bmN0aW9uIEdldC1DYWNoZWRMb2dvblZhbHVlIHsKICAgICRwYXRoPSdIS0xNOlxTT0ZUV0FSRVxNaWNyb3NvZnRcV2luZG93cyBOVFxDdXJyZW50VmVyc2lvblxXaW5sb2dvbicKICAgIHRyeSB7IHJldHVybiBbc3RyaW5nXShHZXQtSXRlbVByb3BlcnR5IC1QYXRoICRwYXRoIC1OYW1lIENhY2hlZExvZ29uc0NvdW50IC1FcnJvckFjdGlvbiBTdG9wKS5DYWNoZWRMb2dvbnNDb3VudCB9IGNhdGNoIHsgcmV0dXJuICRudWxsIH0KfQpmdW5jdGlvbiBHZXQtS2RjUHJveHlTdGF0ZSB7CiAgICAkcm9vdFBhdGg9J0hLTE06XFNPRlRXQVJFXE1pY3Jvc29mdFxXaW5kb3dzXEN1cnJlbnRWZXJzaW9uXFBvbGljaWVzXFN5c3RlbVxLZXJiZXJvcycKICAgICRtYXBwaW5nUGF0aD1Kb2luLVBhdGggJHJvb3RQYXRoICdLZGNQcm94eVNlcnZlcicKICAgICRlbmFibGVkRXhpc3RzPSRmYWxzZTsgJGVuYWJsZWQ9JG51bGw7ICRtYXBwaW5ncz1AKCkKICAgIGlmIChUZXN0LVBhdGggJHJvb3RQYXRoKSB7CiAgICAgICAgJGl0ZW09R2V0LUl0ZW1Qcm9wZXJ0eSAtUGF0aCAkcm9vdFBhdGggLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKICAgICAgICBpZiAoJG51bGwgLW5lICRpdGVtIC1hbmQgJGl0ZW0uUFNPYmplY3QuUHJvcGVydGllcy5OYW1lIC1jb250YWlucyAnS2RjUHJveHlTZXJ2ZXJfRW5hYmxlZCcpIHsgJGVuYWJsZWRFeGlzdHM9JHRydWU7ICRlbmFibGVkPSRpdGVtLktkY1Byb3h5U2VydmVyX0VuYWJsZWQgfQogICAgfQogICAgaWYgKFRlc3QtUGF0aCAkbWFwcGluZ1BhdGgpIHsKICAgICAgICAkbWFwPUdldC1JdGVtUHJvcGVydHkgLVBhdGggJG1hcHBpbmdQYXRoIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlCiAgICAgICAgaWYgKCRudWxsIC1uZSAkbWFwKSB7ICRtYXBwaW5ncz1AKCRtYXAuUFNPYmplY3QuUHJvcGVydGllcyB8IFdoZXJlLU9iamVjdCB7JF8uTmFtZSAtbm90bGlrZSAnUFMqJ30gfCBGb3JFYWNoLU9iamVjdCB7IiQoJF8uTmFtZSk9JChbc3RyaW5nXSRfLlZhbHVlKSJ9IHwgU29ydC1PYmplY3QpIH0KICAgIH0KICAgIFtwc2N1c3RvbW9iamVjdF1Ae2VuYWJsZWRFeGlzdHM9JGVuYWJsZWRFeGlzdHM7ZW5hYmxlZD0kZW5hYmxlZDttYXBwaW5nRXhpc3RzPShUZXN0LVBhdGggJG1hcHBpbmdQYXRoKTttYXBwaW5ncz0kbWFwcGluZ3N9Cn0KCmZ1bmN0aW9uIEdldC1GaXJld2FsbEVuZ2luZVN0YXRlIHsKICAgICRzZXJ2aWNlU3RhdGU9J1VOS05PV04nCiAgICAkZW5hYmxlZFByb2ZpbGVzPUAoKQogICAgJGRpc2FibGVkUHJvZmlsZXM9QCgpCiAgICB0cnkgewogICAgICAgICRzZXJ2aWNlPUdldC1DaW1JbnN0YW5jZSBXaW4zMl9TZXJ2aWNlIC1GaWx0ZXIgIk5hbWU9J01wc1N2YyciIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgJHNlcnZpY2VTdGF0ZT1bc3RyaW5nXSRzZXJ2aWNlLlN0YXRlCiAgICB9IGNhdGNoIHsgfQogICAgdHJ5IHsKICAgICAgICBmb3JlYWNoKCRwcm9maWxlIGluIEAoR2V0LU5ldEZpcmV3YWxsUHJvZmlsZSAtRXJyb3JBY3Rpb24gU3RvcCkpIHsKICAgICAgICAgICAgaWYoW2Jvb2xdJHByb2ZpbGUuRW5hYmxlZCl7JGVuYWJsZWRQcm9maWxlcyArPSBbc3RyaW5nXSRwcm9maWxlLk5hbWV9ZWxzZXskZGlzYWJsZWRQcm9maWxlcyArPSBbc3RyaW5nXSRwcm9maWxlLk5hbWV9CiAgICAgICAgfQogICAgfSBjYXRjaCB7IH0KICAgIFtwc2N1c3RvbW9iamVjdF1AewogICAgICAgIHNlcnZpY2VTdGF0ZT0kc2VydmljZVN0YXRlCiAgICAgICAgZW5hYmxlZFByb2ZpbGVzPUAoJGVuYWJsZWRQcm9maWxlcykKICAgICAgICBkaXNhYmxlZFByb2ZpbGVzPUAoJGRpc2FibGVkUHJvZmlsZXMpCiAgICAgICAgcGFzcz0oJHNlcnZpY2VTdGF0ZSAtZXEgJ1J1bm5pbmcnIC1hbmQgJGVuYWJsZWRQcm9maWxlcy5Db3VudCAtZ3QgMCkKICAgIH0KfQoKZnVuY3Rpb24gTm9ybWFsaXplLVN0YXJ0TW9kZSB7CiAgICBwYXJhbShbc3RyaW5nXSRWYWx1ZSkKICAgIHN3aXRjaCAoJFZhbHVlKSB7ICdBdXRvbWF0aWMnIHsnQXV0byd9ICdBdXRvJyB7J0F1dG8nfSAnTWFudWFsJyB7J01hbnVhbCd9ICdEaXNhYmxlZCcgeydEaXNhYmxlZCd9IGRlZmF1bHQgeyRWYWx1ZX0gfQp9CmZ1bmN0aW9uIENvbXBhcmUtS2RjUHJveHlSZXN0b3JlIHsKICAgIHBhcmFtKCRPcmlnaW5hbCwkQ3VycmVudCkKICAgIGlmICgkbnVsbCAtZXEgJE9yaWdpbmFsKSB7IHJldHVybiAkdHJ1ZSB9CiAgICAkZXhwZWN0ZWRFbmFibGVkRXhpc3RzPVtib29sXShHZXQtUHJvcGVydHlWYWx1ZSAkT3JpZ2luYWwgJ2VuYWJsZWRWYWx1ZUV4aXN0ZWQnICRmYWxzZSkKICAgICRleHBlY3RlZEVuYWJsZWQ9R2V0LVByb3BlcnR5VmFsdWUgJE9yaWdpbmFsICdlbmFibGVkVmFsdWUnICRudWxsCiAgICAkZXhwZWN0ZWRNYXBFeGlzdHM9W2Jvb2xdKEdldC1Qcm9wZXJ0eVZhbHVlICRPcmlnaW5hbCAnbWFwcGluZ1BhdGhFeGlzdGVkJyAkZmFsc2UpCiAgICAkZXhwZWN0ZWRNYXBwaW5ncz1AKEdldC1Qcm9wZXJ0eVZhbHVlICRPcmlnaW5hbCAnbWFwcGluZ3MnIEAoKSB8IEZvckVhY2gtT2JqZWN0IHsiJChbc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkXyAnbmFtZScgJycpKT0kKFtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRfICd2YWx1ZScgJycpKSJ9IHwgU29ydC1PYmplY3QpCiAgICBpZiAoJEN1cnJlbnQuZW5hYmxlZEV4aXN0cyAtbmUgJGV4cGVjdGVkRW5hYmxlZEV4aXN0cykgeyByZXR1cm4gJGZhbHNlIH0KICAgIGlmICgkZXhwZWN0ZWRFbmFibGVkRXhpc3RzIC1hbmQgW3N0cmluZ10kQ3VycmVudC5lbmFibGVkIC1uZSBbc3RyaW5nXSRleHBlY3RlZEVuYWJsZWQpIHsgcmV0dXJuICRmYWxzZSB9CiAgICBpZiAoJEN1cnJlbnQubWFwcGluZ0V4aXN0cyAtbmUgJGV4cGVjdGVkTWFwRXhpc3RzKSB7IHJldHVybiAkZmFsc2UgfQogICAgcmV0dXJuICgoQCgkQ3VycmVudC5tYXBwaW5ncykgLWpvaW4gJ3wnKSAtZXEgKCRleHBlY3RlZE1hcHBpbmdzIC1qb2luICd8JykpCn0KZnVuY3Rpb24gQ29udmVydC1Ub05vcm1hbGl6ZWRTZXQgewogICAgcGFyYW0oJFZhbHVlKQogICAgJGl0ZW1zPUAoKQogICAgZm9yZWFjaCgkZW50cnkgaW4gQCgkVmFsdWUpKSB7CiAgICAgICAgaWYoJG51bGwgLWVxICRlbnRyeSl7Y29udGludWV9CiAgICAgICAgJGl0ZW1zICs9IEAoKFtzdHJpbmddJGVudHJ5IC1zcGxpdCAnLCcpIHwgRm9yRWFjaC1PYmplY3QgeyRfLlRyaW0oKX0gfCBXaGVyZS1PYmplY3QgeyRffSkKICAgIH0KICAgIHJldHVybiBAKCRpdGVtcyB8IFNvcnQtT2JqZWN0IC1VbmlxdWUpCn0KZnVuY3Rpb24gQ29udmVydC1Qcm90b2NvbE5hbWUgewogICAgcGFyYW0oJFZhbHVlKQogICAgJHRleHQ9W3N0cmluZ10kVmFsdWUKICAgIHN3aXRjaCgkdGV4dC5Ub1VwcGVySW52YXJpYW50KCkpIHsnNicgeydUQ1AnfSAnMTcnIHsnVURQJ30gZGVmYXVsdCB7JHRleHQuVG9VcHBlckludmFyaWFudCgpfX0KfQpmdW5jdGlvbiBUZXN0LUZpcmV3YWxsUGxhbiB7CiAgICBwYXJhbSgkUGxhbixbc3RyaW5nXSRPcGVyYXRpb24pCiAgICAkZGVmaW5pdGlvbnM9QChHZXQtUHJvcGVydHlWYWx1ZSAkUGxhbiAncnVsZURlZmluaXRpb25zJyBAKCkpCiAgICBpZiAoJGRlZmluaXRpb25zLkNvdW50IC1lcSAwKSB7IHJldHVybiBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSR0cnVlO0FjdHVhbD0nTm8gcnVsZXMgcmVxdWlyZWQnO0ZhaWx1cmVzPUAoKX0gfQogICAgJGZhaWx1cmVzPUAoKTsgJGFjdHVhbD1AKCkKICAgIGZvcmVhY2goJGRlZmluaXRpb24gaW4gJGRlZmluaXRpb25zKSB7CiAgICAgICAgJG5hbWU9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJGRlZmluaXRpb24gJ25hbWUnICcnKQogICAgICAgICRydWxlPUAoR2V0LU5ldEZpcmV3YWxsUnVsZSAtTmFtZSAkbmFtZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSkKICAgICAgICBpZigkT3BlcmF0aW9uIC1lcSAnUkVTVE9SRScpIHsKICAgICAgICAgICAgaWYoJHJ1bGUuQ291bnQgLWd0IDApeyRmYWlsdXJlcyArPSAiJHtuYW1lfTpzdGlsbC1wcmVzZW50In0KICAgICAgICAgICAgY29udGludWUKICAgICAgICB9CiAgICAgICAgaWYoJHJ1bGUuQ291bnQgLW5lIDEpeyRmYWlsdXJlcyArPSAiJHtuYW1lfTptaXNzaW5nLW9yLWR1cGxpY2F0ZSI7Y29udGludWV9CiAgICAgICAgJGl0ZW09JHJ1bGVbMF0KICAgICAgICBpZihbc3RyaW5nXSRpdGVtLkVuYWJsZWQgLW5lICdUcnVlJyAtb3IgW3N0cmluZ10kaXRlbS5BY3Rpb24gLW5lICdCbG9jaycgLW9yIFtzdHJpbmddJGl0ZW0uRGlyZWN0aW9uIC1uZSAnT3V0Ym91bmQnKXskZmFpbHVyZXMgKz0gIiR7bmFtZX06cnVsZS1wcm9wZXJ0aWVzIn0KICAgICAgICAkcG9ydEZpbHRlcj1HZXQtTmV0RmlyZXdhbGxQb3J0RmlsdGVyIC1Bc3NvY2lhdGVkTmV0RmlyZXdhbGxSdWxlICRpdGVtIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlCiAgICAgICAgJGFkZHJlc3NGaWx0ZXI9R2V0LU5ldEZpcmV3YWxsQWRkcmVzc0ZpbHRlciAtQXNzb2NpYXRlZE5ldEZpcmV3YWxsUnVsZSAkaXRlbSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQogICAgICAgIGlmKCRudWxsIC1lcSAkcG9ydEZpbHRlciAtb3IgJG51bGwgLWVxICRhZGRyZXNzRmlsdGVyKXskZmFpbHVyZXMgKz0gIiR7bmFtZX06ZmlsdGVyLXVucmVhZGFibGUiO2NvbnRpbnVlfQogICAgICAgICRleHBlY3RlZFByb3RvY29sPUNvbnZlcnQtUHJvdG9jb2xOYW1lIChHZXQtUHJvcGVydHlWYWx1ZSAkZGVmaW5pdGlvbiAncHJvdG9jb2wnICcnKQogICAgICAgICRhY3R1YWxQcm90b2NvbD1Db252ZXJ0LVByb3RvY29sTmFtZSAkcG9ydEZpbHRlci5Qcm90b2NvbAogICAgICAgICRleHBlY3RlZFBvcnRzPUNvbnZlcnQtVG9Ob3JtYWxpemVkU2V0IChHZXQtUHJvcGVydHlWYWx1ZSAkZGVmaW5pdGlvbiAncmVtb3RlUG9ydHMnIEAoKSkKICAgICAgICAkYWN0dWFsUG9ydHM9Q29udmVydC1Ub05vcm1hbGl6ZWRTZXQgJHBvcnRGaWx0ZXIuUmVtb3RlUG9ydAogICAgICAgICRleHBlY3RlZEFkZHJlc3Nlcz1Db252ZXJ0LVRvTm9ybWFsaXplZFNldCAoR2V0LVByb3BlcnR5VmFsdWUgJGRlZmluaXRpb24gJ3JlbW90ZUFkZHJlc3NlcycgQCgpKQogICAgICAgICRhY3R1YWxBZGRyZXNzZXM9Q29udmVydC1Ub05vcm1hbGl6ZWRTZXQgJGFkZHJlc3NGaWx0ZXIuUmVtb3RlQWRkcmVzcwogICAgICAgIGlmKCRhY3R1YWxQcm90b2NvbCAtbmUgJGV4cGVjdGVkUHJvdG9jb2wpeyRmYWlsdXJlcyArPSAiJHtuYW1lfTpwcm90b2NvbCJ9CiAgICAgICAgaWYoKCRhY3R1YWxQb3J0cyAtam9pbiAnfCcpIC1uZSAoJGV4cGVjdGVkUG9ydHMgLWpvaW4gJ3wnKSl7JGZhaWx1cmVzICs9ICIke25hbWV9OnJlbW90ZS1wb3J0cyJ9CiAgICAgICAgaWYoKCRhY3R1YWxBZGRyZXNzZXMgLWpvaW4gJ3wnKSAtbmUgKCRleHBlY3RlZEFkZHJlc3NlcyAtam9pbiAnfCcpKXskZmFpbHVyZXMgKz0gIiR7bmFtZX06cmVtb3RlLWFkZHJlc3NlcyJ9CiAgICAgICAgJGFjdHVhbCArPSAiJHtuYW1lfVske2FjdHVhbFByb3RvY29sfTokKCRhY3R1YWxQb3J0cyAtam9pbiAnLCcpPT4kKCRhY3R1YWxBZGRyZXNzZXMgLWpvaW4gJywnKV0iCiAgICB9CiAgICBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSgkZmFpbHVyZXMuQ291bnQgLWVxIDApO0FjdHVhbD0oJGFjdHVhbCAtam9pbiAnIHwgJyk7RmFpbHVyZXM9JGZhaWx1cmVzfQp9Cgp0cnkgewogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFN0YXRlUGF0aCkpIHsgZXhpdCAxMCB9CiAgICAkc3RhdGU9R2V0LUNvbnRlbnQgLUxpdGVyYWxQYXRoICRTdGF0ZVBhdGggLVJhdyAtRXJyb3JBY3Rpb24gU3RvcCB8IENvbnZlcnRGcm9tLUpzb24gLUVycm9yQWN0aW9uIFN0b3AKICAgICRwZW5kaW5nPUdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAncG9zdEJvb3RWZXJpZmljYXRpb24nICRudWxsCiAgICAkb3BlcmF0aW9uPVtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRwZW5kaW5nICdvcGVyYXRpb24nICcnKQogICAgaWYgKCRvcGVyYXRpb24gLW5vdGluIEAoJ0xPQ0tET1dOJywnUkVTVE9SRScpKSB7IGV4aXQgMTEgfQoKICAgIEluaXRpYWxpemUtTHNhTmF0aXZlVHlwZQogICAgSW5pdGlhbGl6ZS1XdHNOYXRpdmVUeXBlCiAgICAkY2hlY2tzPUAoKQogICAgJG9zPUdldC1DaW1JbnN0YW5jZSBXaW4zMl9PcGVyYXRpbmdTeXN0ZW0gLUVycm9yQWN0aW9uIFN0b3AKICAgICRjdXJyZW50Qm9vdD0oW2RhdGV0aW1lXSRvcy5MYXN0Qm9vdFVwVGltZSkuVG9Vbml2ZXJzYWxUaW1lKCkKICAgICRib290QmVmb3JlVGV4dCA9IGlmICgkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKSB7IFtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ2FjdGlvbnMnICRudWxsKSAnYm9vdFRpbWVCZWZvcmUnICcnKSB9IGVsc2UgeyBbc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ3Jlc3RvcmVCb290VGltZUJlZm9yZScgJycpIH0KICAgICRib290QmVmb3JlPSRudWxsCiAgICB0cnkgeyBpZiAoLW5vdCBbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRib290QmVmb3JlVGV4dCkpIHsgJGJvb3RCZWZvcmU9W2RhdGV0aW1lXTo6UGFyc2UoJGJvb3RCZWZvcmVUZXh0KS5Ub1VuaXZlcnNhbFRpbWUoKSB9IH0gY2F0Y2ggeyB9CiAgICAkcmVib290UGFzcz0oJG51bGwgLW5lICRib290QmVmb3JlIC1hbmQgJGN1cnJlbnRCb290IC1ndCAkYm9vdEJlZm9yZSkKICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdSRUJPT1QnIC1TdGF0dXMgJChpZigkcmVib290UGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICdDdXJyZW50IGJvb3QgdGltZSBsYXRlciB0aGFuIHByZS1jaGFuZ2UgYm9vdCB0aW1lJyAtQWN0dWFsICRjdXJyZW50Qm9vdC5Ub1N0cmluZygnbycpCgogICAgJGRpcmVjdG9yeUFjbEJlZm9yZT1UZXN0LVNlY3VyZUFjbCAtUGF0aCAkQmFzZURpcmVjdG9yeQogICAgJGRpcmVjdG9yeUFjbEFmdGVyPSRkaXJlY3RvcnlBY2xCZWZvcmUKICAgICRkaXJlY3RvcnlBY2xSZXBhaXJlZD0kZmFsc2UKICAgIGlmKC1ub3QgJGRpcmVjdG9yeUFjbEJlZm9yZS5QYXNzKXsKICAgICAgICB0cnl7U2V0LVNlY3VyZURpcmVjdG9yeUFjbCAtUGF0aCAkQmFzZURpcmVjdG9yeTskZGlyZWN0b3J5QWNsQWZ0ZXI9VGVzdC1TZWN1cmVBY2wgLVBhdGggJEJhc2VEaXJlY3Rvcnk7JGRpcmVjdG9yeUFjbFJlcGFpcmVkPVtib29sXSRkaXJlY3RvcnlBY2xBZnRlci5QYXNzfWNhdGNoe30KICAgIH0KICAgICRkaXJlY3RvcnlBY2xTdGF0dXM9aWYoJGRpcmVjdG9yeUFjbEJlZm9yZS5QYXNzKXsnUEFTUyd9ZWxzZWlmKCRkaXJlY3RvcnlBY2xSZXBhaXJlZCl7J1dBUk5JTkcnfWVsc2V7J0ZBSUwnfQogICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1NUQVRFX0RJUkVDVE9SWV9BQ0wnIC1TdGF0dXMgJGRpcmVjdG9yeUFjbFN0YXR1cyAtRXhwZWN0ZWQgJ093bmVyIFNZU1RFTSwgcHJvdGVjdGVkIEFDTCwgb25seSBTWVNURU0gYW5kIEFkbWluaXN0cmF0b3JzJyAtQWN0dWFsICJCZWZvcmVPd25lcj0kKCRkaXJlY3RvcnlBY2xCZWZvcmUuT3duZXIpO0JlZm9yZVByb3RlY3RlZD0kKCRkaXJlY3RvcnlBY2xCZWZvcmUuUHJvdGVjdGVkKTtCZWZvcmVVbmV4cGVjdGVkPSQoJGRpcmVjdG9yeUFjbEJlZm9yZS5VbmV4cGVjdGVkKTtSZXBhaXJlZD0kZGlyZWN0b3J5QWNsUmVwYWlyZWQ7QWZ0ZXJVbmV4cGVjdGVkPSQoJGRpcmVjdG9yeUFjbEFmdGVyLlVuZXhwZWN0ZWQpIiAtTWVzc2FnZSAkKGlmKCRkaXJlY3RvcnlBY2xSZXBhaXJlZCl7J1VuZXhwZWN0ZWQgZGlyZWN0b3J5IEFDTCBlbnRyaWVzIHdlcmUgcmVtb3ZlZCBieSB0aGUgcG9zdC1ib290IHZlcmlmaWVyLid9ZWxzZXskbnVsbH0pCgogICAgJGFjbEJlZm9yZT1UZXN0LVNlY3VyZUFjbCAtUGF0aCAkU3RhdGVQYXRoCiAgICAkYWNsQWZ0ZXI9JGFjbEJlZm9yZQogICAgJGFjbFJlcGFpcmVkPSRmYWxzZQogICAgaWYoLW5vdCAkYWNsQmVmb3JlLlBhc3MpewogICAgICAgIHRyeXtTZXQtU2VjdXJlRmlsZUFjbCAtUGF0aCAkU3RhdGVQYXRoOyRhY2xBZnRlcj1UZXN0LVNlY3VyZUFjbCAtUGF0aCAkU3RhdGVQYXRoOyRhY2xSZXBhaXJlZD1bYm9vbF0kYWNsQWZ0ZXIuUGFzc31jYXRjaHt9CiAgICB9CiAgICAkYWNsU3RhdHVzPWlmKCRhY2xCZWZvcmUuUGFzcyl7J1BBU1MnfWVsc2VpZigkYWNsUmVwYWlyZWQpeydXQVJOSU5HJ31lbHNleydGQUlMJ30KICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdTVEFURV9GSUxFX0FDTCcgLVN0YXR1cyAkYWNsU3RhdHVzIC1FeHBlY3RlZCAnT3duZXIgU1lTVEVNLCBwcm90ZWN0ZWQgQUNMLCBvbmx5IFNZU1RFTSBhbmQgQWRtaW5pc3RyYXRvcnMnIC1BY3R1YWwgIkJlZm9yZU93bmVyPSQoJGFjbEJlZm9yZS5Pd25lcik7QmVmb3JlUHJvdGVjdGVkPSQoJGFjbEJlZm9yZS5Qcm90ZWN0ZWQpO0JlZm9yZVVuZXhwZWN0ZWQ9JCgkYWNsQmVmb3JlLlVuZXhwZWN0ZWQpO1JlcGFpcmVkPSRhY2xSZXBhaXJlZDtBZnRlclVuZXhwZWN0ZWQ9JCgkYWNsQWZ0ZXIuVW5leHBlY3RlZCkiIC1NZXNzYWdlICQoaWYoJGFjbFJlcGFpcmVkKXsnVW5leHBlY3RlZCBzdGF0ZS1maWxlIEFDTCBlbnRyaWVzIHdlcmUgcmVtb3ZlZCBieSB0aGUgcG9zdC1ib290IHZlcmlmaWVyLid9ZWxzZXskbnVsbH0pCgogICAgJHZlcmlmaWVyQWNsQmVmb3JlPVRlc3QtU2VjdXJlQWNsIC1QYXRoICRQU0NvbW1hbmRQYXRoCiAgICAkdmVyaWZpZXJBY2xBZnRlcj0kdmVyaWZpZXJBY2xCZWZvcmUKICAgICR2ZXJpZmllckFjbFJlcGFpcmVkPSRmYWxzZQogICAgaWYoLW5vdCAkdmVyaWZpZXJBY2xCZWZvcmUuUGFzcyl7CiAgICAgICAgdHJ5e1NldC1TZWN1cmVGaWxlQWNsIC1QYXRoICRQU0NvbW1hbmRQYXRoOyR2ZXJpZmllckFjbEFmdGVyPVRlc3QtU2VjdXJlQWNsIC1QYXRoICRQU0NvbW1hbmRQYXRoOyR2ZXJpZmllckFjbFJlcGFpcmVkPVtib29sXSR2ZXJpZmllckFjbEFmdGVyLlBhc3N9Y2F0Y2h7fQogICAgfQogICAgJHZlcmlmaWVyQWNsU3RhdHVzPWlmKCR2ZXJpZmllckFjbEJlZm9yZS5QYXNzKXsnUEFTUyd9ZWxzZWlmKCR2ZXJpZmllckFjbFJlcGFpcmVkKXsnV0FSTklORyd9ZWxzZXsnRkFJTCd9CiAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnVkVSSUZJRVJfRklMRV9BQ0wnIC1TdGF0dXMgJHZlcmlmaWVyQWNsU3RhdHVzIC1FeHBlY3RlZCAnT3duZXIgU1lTVEVNLCBwcm90ZWN0ZWQgQUNMLCBvbmx5IFNZU1RFTSBhbmQgQWRtaW5pc3RyYXRvcnMnIC1BY3R1YWwgIkJlZm9yZU93bmVyPSQoJHZlcmlmaWVyQWNsQmVmb3JlLk93bmVyKTtCZWZvcmVQcm90ZWN0ZWQ9JCgkdmVyaWZpZXJBY2xCZWZvcmUuUHJvdGVjdGVkKTtCZWZvcmVVbmV4cGVjdGVkPSQoJHZlcmlmaWVyQWNsQmVmb3JlLlVuZXhwZWN0ZWQpO1JlcGFpcmVkPSR2ZXJpZmllckFjbFJlcGFpcmVkO0FmdGVyVW5leHBlY3RlZD0kKCR2ZXJpZmllckFjbEFmdGVyLlVuZXhwZWN0ZWQpIiAtTWVzc2FnZSAkKGlmKCR2ZXJpZmllckFjbFJlcGFpcmVkKXsnVW5leHBlY3RlZCB2ZXJpZmllci1maWxlIEFDTCBlbnRyaWVzIHdlcmUgcmVtb3ZlZCBieSB0aGUgcG9zdC1ib290IHZlcmlmaWVyLid9ZWxzZXskbnVsbH0pCgogICAgJGN1cnJlbnRDYWNoZT1HZXQtQ2FjaGVkTG9nb25WYWx1ZQogICAgaWYgKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicpIHsKICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnQ0FDSEVEX0RPTUFJTl9MT0dPTicgLVN0YXR1cyAkKGlmKCRjdXJyZW50Q2FjaGUgLWVxICcwJyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICcwJyAtQWN0dWFsICQoaWYoJG51bGwgLWVxICRjdXJyZW50Q2FjaGUpeyc8VkFMVUVfQUJTRU5UPid9ZWxzZXskY3VycmVudENhY2hlfSkKICAgIH0gZWxzZSB7CiAgICAgICAgJG9yaWdpbmFsPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ29yaWdpbmFsU3RhdGUnICRudWxsKSAnY2FjaGVkTG9nb25zQ291bnQnICRudWxsCiAgICAgICAgJGV4cGVjdGVkPWlmKFtib29sXShHZXQtUHJvcGVydHlWYWx1ZSAkb3JpZ2luYWwgJ2V4aXN0ZWQnICRmYWxzZSkpe1tzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRvcmlnaW5hbCAndmFsdWUnICcnKX1lbHNleyc8VkFMVUVfQUJTRU5UPid9CiAgICAgICAgJGFjdHVhbD1pZigkbnVsbCAtZXEgJGN1cnJlbnRDYWNoZSl7JzxWQUxVRV9BQlNFTlQ+J31lbHNleyRjdXJyZW50Q2FjaGV9CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0NBQ0hFRF9ET01BSU5fTE9HT04nIC1TdGF0dXMgJChpZigkYWN0dWFsIC1lcSAkZXhwZWN0ZWQpeydQQVNTJ31lbHNleydGQUlMJ30pIC1FeHBlY3RlZCAkZXhwZWN0ZWQgLUFjdHVhbCAkYWN0dWFsCiAgICB9CgogICAgJHJpZ2h0c0ZhaWx1cmVzPUAoKTsgJHJpZ2h0c0NoZWNrZWQ9MDsgJGJhc2VsaW5lV2FybmluZ3M9QCgpOyAkYmFzZWxpbmVDaGVja2VkPTAKICAgICRyZWNvcmRzPUAoR2V0LVByb3BlcnR5VmFsdWUgKEdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAnb3JpZ2luYWxTdGF0ZScgJG51bGwpICd1c2VyUmlnaHRzJyBAKCkpCiAgICBmb3JlYWNoKCRyZWNvcmQgaW4gJHJlY29yZHMpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICAkc2lkPVtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRyZWNvcmQgJ3NpZCcgJycpCiAgICAgICAgICAgIGlmIChbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRzaWQpKSB7IGNvbnRpbnVlIH0KICAgICAgICAgICAgJGFjY291bnRMYWJlbD1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkcmVjb3JkICdhY2NvdW50TmFtZScgJHNpZCkKICAgICAgICAgICAgJGN1cnJlbnRSaWdodHM9QChbRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3ROYXRpdmVWMTQwXTo6R2V0UmlnaHRzKCRzaWQpKQogICAgICAgICAgICBpZigkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKSB7CiAgICAgICAgICAgICAgICBmb3JlYWNoKCRyaWdodCBpbiBAKEdldC1Qcm9wZXJ0eVZhbHVlICRyZWNvcmQgJ3JlcXVpcmVkUmlnaHRzJyBAKCkpKSB7CiAgICAgICAgICAgICAgICAgICAgJHJpZ2h0c0NoZWNrZWQrKwogICAgICAgICAgICAgICAgICAgIGlmKCRyaWdodCAtbm90aW4gJGN1cnJlbnRSaWdodHMpeyRyaWdodHNGYWlsdXJlcyArPSAiJHthY2NvdW50TGFiZWx9OiR7cmlnaHR9Om1pc3NpbmcifQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgZm9yZWFjaCgkcmlnaHQgaW4gQChHZXQtUHJvcGVydHlWYWx1ZSAkcmVjb3JkICdyaWdodHNBZGRlZCcgQCgpKSkgewogICAgICAgICAgICAgICAgICAgICRyaWdodHNDaGVja2VkKysKICAgICAgICAgICAgICAgICAgICBpZigkcmlnaHQgLWluICRjdXJyZW50UmlnaHRzKXskcmlnaHRzRmFpbHVyZXMgKz0gIiR7YWNjb3VudExhYmVsfToke3JpZ2h0fTpzdGlsbC1wcmVzZW50In0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGZvcmVhY2goJHJpZ2h0IGluIEAoR2V0LVByb3BlcnR5VmFsdWUgJHJlY29yZCAncmlnaHRzQmVmb3JlJyBAKCkpKSB7CiAgICAgICAgICAgICAgICAgICAgJGJhc2VsaW5lQ2hlY2tlZCsrCiAgICAgICAgICAgICAgICAgICAgaWYoJHJpZ2h0IC1ub3RpbiAkY3VycmVudFJpZ2h0cyl7JGJhc2VsaW5lV2FybmluZ3MgKz0gIiR7YWNjb3VudExhYmVsfToke3JpZ2h0fTpvcmlnaW5hbC1yaWdodC1taXNzaW5nIn0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggeyAkcmlnaHRzRmFpbHVyZXMgKz0gIiQoW3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJHJlY29yZCAnYWNjb3VudE5hbWUnICd1bmtub3duJykpOmVudW1lcmF0aW9uLWZhaWxlZDokKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSIgfQogICAgfQogICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1VTRVJfUklHSFRTJyAtU3RhdHVzICQoaWYoJHJpZ2h0c0ZhaWx1cmVzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJChpZigkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKXsnQWxsIHJlcXVpcmVkIGRlbnkgcmlnaHRzIHByZXNlbnQnfWVsc2V7J0FsbCBMb2NrZG93bi1hZGRlZCBkZW55IHJpZ2h0cyBhYnNlbnQnfSkgLUFjdHVhbCAkKGlmKCRyaWdodHNGYWlsdXJlcy5Db3VudCAtZXEgMCl7IkNoZWNrZWQgJHJpZ2h0c0NoZWNrZWQgYXNzaWdubWVudHMifWVsc2V7JHJpZ2h0c0ZhaWx1cmVzIC1qb2luICcgfCAnfSkKCiAgICAkc2NvcGVHdWFyZD1HZXQtUHJvcGVydHlWYWx1ZSAoR2V0LVByb3BlcnR5VmFsdWUgJHN0YXRlICdhY3Rpb25zJyAkbnVsbCkgJ3Njb3BlR3VhcmQnICRudWxsCiAgICAkc2NvcGVHdWFyZFByaW5jaXBhbHM9QChHZXQtUHJvcGVydHlWYWx1ZSAkc2NvcGVHdWFyZCAncHJpbmNpcGFscycgQCgpKQogICAgaWYoJG9wZXJhdGlvbiAtZXEgJ0xPQ0tET1dOJyAtYW5kICRzY29wZUd1YXJkUHJpbmNpcGFscy5Db3VudCAtZ3QgMCkgewogICAgICAgICRzY29wZUd1YXJkRmFpbHVyZXM9QCgpCiAgICAgICAgZm9yZWFjaCgkcHJpbmNpcGFsIGluICRzY29wZUd1YXJkUHJpbmNpcGFscykgewogICAgICAgICAgICAkZ3VhcmRTaWQ9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJHByaW5jaXBhbCAnc2lkJyAnJykKICAgICAgICAgICAgaWYoW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkZ3VhcmRTaWQpKXtjb250aW51ZX0KICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICRndWFyZFJpZ2h0cz1AKFtFbWVyZ2VuY3lTZWN1cml0eS5Qb3N0Qm9vdE5hdGl2ZVYxNDBdOjpHZXRSaWdodHMoJGd1YXJkU2lkKSkKICAgICAgICAgICAgICAgIGZvcmVhY2goJHJpZ2h0IGluIEAoJ1NlRGVueUludGVyYWN0aXZlTG9nb25SaWdodCcsJ1NlRGVueVJlbW90ZUludGVyYWN0aXZlTG9nb25SaWdodCcsJ1NlRGVueU5ldHdvcmtMb2dvblJpZ2h0JykpIHsKICAgICAgICAgICAgICAgICAgICBpZigkcmlnaHQgLW5vdGluICRndWFyZFJpZ2h0cyl7JHNjb3BlR3VhcmRGYWlsdXJlcyArPSAiJHtndWFyZFNpZH06JHtyaWdodH06bWlzc2luZyJ9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gY2F0Y2ggeyRzY29wZUd1YXJkRmFpbHVyZXMgKz0gIiR7Z3VhcmRTaWR9OmVudW1lcmF0aW9uLWZhaWxlZDokKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSJ9CiAgICAgICAgfQogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdTQ09QRV9HVUFSRCcgLVN0YXR1cyAkKGlmKCRzY29wZUd1YXJkRmFpbHVyZXMuQ291bnQgLWVxIDApeydQQVNTJ31lbHNleydGQUlMJ30pIC1FeHBlY3RlZCAnQWxsIGJyb2FkIEFMTC1zY29wZSBpbnRlcmFjdGl2ZS9SRFAvbmV0d29yayBndWFyZCByaWdodHMgcHJlc2VudCcgLUFjdHVhbCAkKGlmKCRzY29wZUd1YXJkRmFpbHVyZXMuQ291bnQgLWVxIDApeyIkKCRzY29wZUd1YXJkUHJpbmNpcGFscy5Db3VudCkgZ3VhcmQgcHJpbmNpcGFsKHMpIHZlcmlmaWVkIn1lbHNleyRzY29wZUd1YXJkRmFpbHVyZXMgLWpvaW4gJyB8ICd9KQogICAgfSBlbHNlaWYoJG9wZXJhdGlvbiAtZXEgJ1JFU1RPUkUnIC1hbmQgJHNjb3BlR3VhcmRQcmluY2lwYWxzLkNvdW50IC1ndCAwKSB7CiAgICAgICAgJHNjb3BlR3VhcmRGYWlsdXJlcz1AKCkKICAgICAgICBmb3JlYWNoKCRwcmluY2lwYWwgaW4gJHNjb3BlR3VhcmRQcmluY2lwYWxzKSB7CiAgICAgICAgICAgICRndWFyZFNpZD1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkcHJpbmNpcGFsICdzaWQnICcnKQogICAgICAgICAgICBpZihbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRndWFyZFNpZCkpe2NvbnRpbnVlfQogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgJGd1YXJkUmlnaHRzPUAoW0VtZXJnZW5jeVNlY3VyaXR5LlBvc3RCb290TmF0aXZlVjE0MF06OkdldFJpZ2h0cygkZ3VhcmRTaWQpKQogICAgICAgICAgICAgICAgZm9yZWFjaCgkcmlnaHQgaW4gQCgnU2VEZW55SW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55UmVtb3RlSW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55TmV0d29ya0xvZ29uUmlnaHQnKSkgewogICAgICAgICAgICAgICAgICAgICRyZWNvcmQ9QCgkcmVjb3JkcyB8IFdoZXJlLU9iamVjdCB7W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJF8gJ3NpZCcgJycpIC1lcSAkZ3VhcmRTaWR9IHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkKICAgICAgICAgICAgICAgICAgICBpZigkcmVjb3JkLkNvdW50IC1ndCAwIC1hbmQgJHJpZ2h0IC1pbiBAKEdldC1Qcm9wZXJ0eVZhbHVlICRyZWNvcmRbMF0gJ3JpZ2h0c0FkZGVkJyBAKCkpIC1hbmQgJHJpZ2h0IC1pbiAkZ3VhcmRSaWdodHMpeyRzY29wZUd1YXJkRmFpbHVyZXMgKz0gIiR7Z3VhcmRTaWR9OiR7cmlnaHR9OnN0aWxsLXByZXNlbnQifQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGNhdGNoIHskc2NvcGVHdWFyZEZhaWx1cmVzICs9ICIke2d1YXJkU2lkfTplbnVtZXJhdGlvbi1mYWlsZWQ6JCgkXy5FeGNlcHRpb24uTWVzc2FnZSkifQogICAgICAgIH0KICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnU0NPUEVfR1VBUkQnIC1TdGF0dXMgJChpZigkc2NvcGVHdWFyZEZhaWx1cmVzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ0xvY2tkb3duLWFkZGVkIGJyb2FkIHNjb3BlLWd1YXJkIHJpZ2h0cyBhYnNlbnQnIC1BY3R1YWwgJChpZigkc2NvcGVHdWFyZEZhaWx1cmVzLkNvdW50IC1lcSAwKXsiJCgkc2NvcGVHdWFyZFByaW5jaXBhbHMuQ291bnQpIGd1YXJkIHByaW5jaXBhbChzKSByZXN0b3JlZCJ9ZWxzZXskc2NvcGVHdWFyZEZhaWx1cmVzIC1qb2luICcgfCAnfSkKICAgIH0gZWxzZSB7CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1NDT1BFX0dVQVJEJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgc2NvcGUgZ3VhcmQnIC1BY3R1YWwgJ05vIHNjb3BlIGd1YXJkIHJlY29yZGVkJwogICAgfQoKICAgIGlmKCRvcGVyYXRpb24gLWVxICdSRVNUT1JFJykgewogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdVU0VSX1JJR0hUU19PUklHSU5BTF9CQVNFTElORScgLVN0YXR1cyAkKGlmKCRiYXNlbGluZVdhcm5pbmdzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnV0FSTklORyd9KSAtRXhwZWN0ZWQgJ1ByZS1leGlzdGluZyB1c2VyIHJpZ2h0cyByZW1haW4gcHJlc2VudCcgLUFjdHVhbCAkKGlmKCRiYXNlbGluZVdhcm5pbmdzLkNvdW50IC1lcSAwKXsiQ2hlY2tlZCAkYmFzZWxpbmVDaGVja2VkIG9yaWdpbmFsIGFzc2lnbm1lbnRzIn1lbHNleyRiYXNlbGluZVdhcm5pbmdzIC1qb2luICcgfCAnfSkKICAgIH0KICAgIGVsc2UgewogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdVU0VSX1JJR0hUU19PUklHSU5BTF9CQVNFTElORScgLVN0YXR1cyAnTk9UX0FQUExJQ0FCTEUnIC1FeHBlY3RlZCAnUmVzdG9yZSBvcGVyYXRpb24nIC1BY3R1YWwgJ05vdCBldmFsdWF0ZWQgZHVyaW5nIExvY2tkb3duJwogICAgfQoKICAgIGlmKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicpIHsKICAgICAgICAkcmVzb2x2ZWRUYXJnZXRzPUAoR2V0LVByb3BlcnR5VmFsdWUgJHN0YXRlICd0YXJnZXRzJyBAKCkgfCBXaGVyZS1PYmplY3QgeyRfLnJlc29sdmVTdGF0dXMgLWVxICdTVUNDRVNTJ30pCiAgICAgICAgJHRhcmdldE5hbWVzPUAoKQogICAgICAgIGZvcmVhY2goJHRhcmdldCBpbiAkcmVzb2x2ZWRUYXJnZXRzKSB7CiAgICAgICAgICAgIGZvcmVhY2goJGZpZWxkIGluIEAoJ2FjY291bnROYW1lJywnbm9ybWFsaXplZCcsJ3JlcXVlc3RlZCcpKSB7CiAgICAgICAgICAgICAgICAkdmFsdWU9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJHRhcmdldCAkZmllbGQgJycpCiAgICAgICAgICAgICAgICBpZigtbm90IFtzdHJpbmddOjpJc051bGxPcldoaXRlU3BhY2UoJHZhbHVlKSl7JHRhcmdldE5hbWVzICs9IChOb3JtYWxpemUtU2Vzc2lvbkFjY291bnROYW1lICR2YWx1ZSl9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgJHRhcmdldE5hbWVzPUAoJHRhcmdldE5hbWVzIHwgU2VsZWN0LU9iamVjdCAtVW5pcXVlKQogICAgICAgICRhY3RpdmVTZXNzaW9ucz1AKEdldC1XdHNTZXNzaW9ucykKICAgICAgICAkcmVtYWluaW5nVGFyZ2V0U2Vzc2lvbnM9QCgkYWN0aXZlU2Vzc2lvbnMgfCBXaGVyZS1PYmplY3QgeyhOb3JtYWxpemUtU2Vzc2lvbkFjY291bnROYW1lIChbc3RyaW5nXSRfLmFjY291bnROYW1lKSkgLWluICR0YXJnZXROYW1lc30pCiAgICAgICAgJHNlc3Npb25QYXNzPSgkcmVtYWluaW5nVGFyZ2V0U2Vzc2lvbnMuQ291bnQgLWVxIDApCiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1RBUkdFVF9TRVNTSU9OU19BQlNFTlQnIC1TdGF0dXMgJChpZigkc2Vzc2lvblBhc3MpeydQQVNTJ31lbHNleydGQUlMJ30pIC1FeHBlY3RlZCAnTm8gcmVzb2x2ZWQgdGFyZ2V0IGlkZW50aXR5IGhhcyBhbiBpbnRlcmFjdGl2ZSBzZXNzaW9uIGFmdGVyIHJlYm9vdCcgLUFjdHVhbCAkKGlmKCRzZXNzaW9uUGFzcyl7JzAgdGFyZ2V0IHNlc3Npb25zJ31lbHNleygoJHJlbWFpbmluZ1RhcmdldFNlc3Npb25zIHwgRm9yRWFjaC1PYmplY3QgeyIkKCRfLnNlc3Npb25JZCk6JCgkXy5hY2NvdW50TmFtZSkifSkgLWpvaW4gJyB8ICcpfSkKICAgICAgICAkYWN0aW9ucz1HZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ2FjdGlvbnMnICRudWxsCiAgICAgICAgaWYoJG51bGwgLW5lICRhY3Rpb25zKSB7CiAgICAgICAgICAgICRhYnNlbnRDb3VudD1baW50XVtNYXRoXTo6TWF4KDAsICgkcmVzb2x2ZWRUYXJnZXRzLkNvdW50IC0gJHJlbWFpbmluZ1RhcmdldFNlc3Npb25zLkNvdW50KSkKICAgICAgICAgICAgJHRlbGVtZXRyeT1HZXQtUHJvcGVydHlWYWx1ZSAkYWN0aW9ucyAnc2Vzc2lvblRlbGVtZXRyeScgJG51bGwKICAgICAgICAgICAgaWYoJG51bGwgLWVxICR0ZWxlbWV0cnkpewogICAgICAgICAgICAgICAgJHRlbGVtZXRyeT1bcHNjdXN0b21vYmplY3RdW29yZGVyZWRdQHt0YXJnZXRTZXNzaW9uc0ZvdW5kPTA7bG9nb2ZmUmVxdWVzdHNBY2NlcHRlZD0wO2xvZ29mZlJlcXVlc3RzRmFpbGVkPTA7Y29uZmlybWVkQ2xvc2VkQmVmb3JlUmVib290PTA7cmVtYWluaW5nU2Vzc2lvbklkc0JlZm9yZVJlYm9vdD1AKCk7dGFyZ2V0SWRlbnRpdGllc1dpdGhvdXRTZXNzaW9uQWZ0ZXJSZWJvb3Q9MDtyZW1haW5pbmdUYXJnZXRTZXNzaW9uc0FmdGVyUmVib290PUAoKTtwZW5kaW5nTG9nb2ZmU2Vzc2lvbklkcz1AKCl9CiAgICAgICAgICAgICAgICAkYWN0aW9uc3xBZGQtTWVtYmVyIC1Ob3RlUHJvcGVydHlOYW1lIHNlc3Npb25UZWxlbWV0cnkgLU5vdGVQcm9wZXJ0eVZhbHVlICR0ZWxlbWV0cnkKICAgICAgICAgICAgfQogICAgICAgICAgICAkdGVsZW1ldHJ5LnRhcmdldElkZW50aXRpZXNXaXRob3V0U2Vzc2lvbkFmdGVyUmVib290PSRhYnNlbnRDb3VudAogICAgICAgICAgICAkdGVsZW1ldHJ5LnJlbWFpbmluZ1RhcmdldFNlc3Npb25zQWZ0ZXJSZWJvb3Q9QCgkcmVtYWluaW5nVGFyZ2V0U2Vzc2lvbnMpCiAgICAgICAgfQogICAgfQogICAgZWxzZSB7CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1RBUkdFVF9TRVNTSU9OU19BQlNFTlQnIC1TdGF0dXMgJ05PVF9BUFBMSUNBQkxFJyAtRXhwZWN0ZWQgJ0xvY2tkb3duIG9wZXJhdGlvbicgLUFjdHVhbCAnUmVzdG9yZSBkb2VzIG5vdCBmb3JjZSBhIHVzZXIgc2Vzc2lvbiB0byByZW1haW4gYWJzZW50JwogICAgfQoKICAgICRzY29wZT1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ3Njb3BlJyAnJykKICAgIGlmKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicgLWFuZCAkc2NvcGUgLWVxICdBTExfVVNFUlNfU0FWRV9MQVBTJykgewogICAgICAgICRsYXBzPUdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAnbGFwcycgJG51bGwKICAgICAgICAkbGFwc1NpZD1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkbGFwcyAnYWNjb3VudFNpZCcgJycpCiAgICAgICAgJGxhcHNOYW1lPVtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRsYXBzICdhY2NvdW50TmFtZScgJycpCiAgICAgICAgJGxhcHNGYWlsdXJlcz1AKCkKICAgICAgICB0cnkgewogICAgICAgICAgICBpZihbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRsYXBzU2lkKSl7dGhyb3cgJ0xBUFMgYWNjb3VudCBTSUQgaXMgYWJzZW50IGZyb20gc3RhdGUuJ30KICAgICAgICAgICAgJGxvY2FsQWNjb3VudD1AKEdldC1DaW1JbnN0YW5jZSBXaW4zMl9Vc2VyQWNjb3VudCAtRmlsdGVyICdMb2NhbEFjY291bnQ9VHJ1ZScgLUVycm9yQWN0aW9uIFN0b3AgfCBXaGVyZS1PYmplY3QgeyRfLlNJRCAtZXEgJGxhcHNTaWR9IHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkKICAgICAgICAgICAgaWYoJGxvY2FsQWNjb3VudC5Db3VudCAtZXEgMCl7JGxhcHNGYWlsdXJlcyArPSAnYWNjb3VudC1ub3QtZm91bmQnfQogICAgICAgICAgICBlbHNlaWYoW2Jvb2xdJGxvY2FsQWNjb3VudFswXS5EaXNhYmxlZCl7JGxhcHNGYWlsdXJlcyArPSAnYWNjb3VudC1kaXNhYmxlZCd9CiAgICAgICAgICAgICRhZG1pbkdyb3VwPUAoR2V0LUNpbUluc3RhbmNlIFdpbjMyX0dyb3VwIC1GaWx0ZXIgIkxvY2FsQWNjb3VudD1UcnVlIEFORCBTSUQ9J1MtMS01LTMyLTU0NCciIC1FcnJvckFjdGlvbiBTdG9wIHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkKICAgICAgICAgICAgJGFkbWluU2lkcz1AKCkKICAgICAgICAgICAgaWYoJGFkbWluR3JvdXAuQ291bnQgLWd0IDApewogICAgICAgICAgICAgICAgdHJ5eyRhZG1pblNpZHM9QChbRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3ROYXRpdmVWMTQwXTo6R2V0TG9jYWxHcm91cE1lbWJlclNpZHMoW3N0cmluZ10kYWRtaW5Hcm91cFswXS5OYW1lKSl9Y2F0Y2h7fQogICAgICAgICAgICAgICAgaWYoJGFkbWluU2lkcy5Db3VudCAtZXEgMCl7CiAgICAgICAgICAgICAgICAgICAgdHJ5eyRhZG1pblNpZHM9QChHZXQtQ2ltQXNzb2NpYXRlZEluc3RhbmNlIC1JbnB1dE9iamVjdCAkYWRtaW5Hcm91cFswXSAtQXNzb2NpYXRpb24gV2luMzJfR3JvdXBVc2VyIC1FcnJvckFjdGlvbiBTdG9wIHwgRm9yRWFjaC1PYmplY3Qge1tzdHJpbmddJF8uU0lEfSl9Y2F0Y2h7fQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmKCRsYXBzU2lkIC1ub3RpbiAkYWRtaW5TaWRzKXskbGFwc0ZhaWx1cmVzICs9ICdub3QtbG9jYWwtYWRtaW5pc3RyYXRvcid9CiAgICAgICAgICAgICRsYXBzUmlnaHRzPUAoW0VtZXJnZW5jeVNlY3VyaXR5LlBvc3RCb290TmF0aXZlVjE0MF06OkdldFJpZ2h0cygkbGFwc1NpZCkpCiAgICAgICAgICAgICRibG9ja2luZz1AKCRsYXBzUmlnaHRzIHwgV2hlcmUtT2JqZWN0IHskXyAtaW4gQCgnU2VEZW55SW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55UmVtb3RlSW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55TmV0d29ya0xvZ29uUmlnaHQnKX0pCiAgICAgICAgICAgIGlmKCRibG9ja2luZy5Db3VudCAtZ3QgMCl7JGxhcHNGYWlsdXJlcyArPSAoJ2Jsb2NraW5nLXJpZ2h0cz0nICsgKCRibG9ja2luZyAtam9pbiAnLCcpKX0KICAgICAgICB9CiAgICAgICAgY2F0Y2ggeyRsYXBzRmFpbHVyZXMgKz0gKCd2ZXJpZmljYXRpb24tZXJyb3I9JyArICRfLkV4Y2VwdGlvbi5NZXNzYWdlKX0KICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnU0FWRUxBUFNfUkVDT1ZFUllfSURFTlRJVFknIC1TdGF0dXMgJChpZigkbGFwc0ZhaWx1cmVzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ1ZlcmlmaWVkIExBUFMgYWNjb3VudCBleGlzdHMsIGlzIGVuYWJsZWQsIGlzIGxvY2FsIGFkbWluaXN0cmF0b3IsIGFuZCBoYXMgbm8gaHVtYW4tYWNjZXNzIGRlbnkgcmlnaHRzJyAtQWN0dWFsICQoaWYoJGxhcHNGYWlsdXJlcy5Db3VudCAtZXEgMCl7IiR7bGFwc05hbWV9OyR7bGFwc1NpZH07dXNhYmxlIn1lbHNleyRsYXBzRmFpbHVyZXMgLWpvaW4gJyB8ICd9KQogICAgfQogICAgZWxzZSB7CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1NBVkVMQVBTX1JFQ09WRVJZX0lERU5USVRZJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgLVNhdmVMYXBzIExvY2tkb3duJyAtQWN0dWFsICdOb3QgcmVxdWVzdGVkIG9yIFJlc3RvcmUgb3BlcmF0aW9uJwogICAgfQoKICAgICRhbGxTY29wZT0kc2NvcGUgLWluIEAoJ0FMTF9VU0VSUycsJ0FMTF9VU0VSU19TQVZFX0xBUFMnKQogICAgJGRvbWFpbkNvbnRleHQ9R2V0LVByb3BlcnR5VmFsdWUgKEdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAnYWN0aW9ucycgJG51bGwpICdkb21haW5Db250ZXh0JyAkbnVsbAogICAgJHBhcnRPZkRvbWFpbj1bYm9vbF0oR2V0LVByb3BlcnR5VmFsdWUgJGRvbWFpbkNvbnRleHQgJ3BhcnRPZkRvbWFpbicgJGZhbHNlKQogICAgaWYoJGFsbFNjb3BlIC1hbmQgJHBhcnRPZkRvbWFpbikgewogICAgICAgICRwbGFuPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ2FjdGlvbnMnICRudWxsKSAnYXV0aEZpcmV3YWxsJyAkbnVsbAogICAgICAgIGlmKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicpIHsKICAgICAgICAgICAgJGZpcmV3YWxsRW5naW5lPUdldC1GaXJld2FsbEVuZ2luZVN0YXRlCiAgICAgICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdGSVJFV0FMTF9FTkdJTkUnIC1TdGF0dXMgJChpZigkZmlyZXdhbGxFbmdpbmUucGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICdNcHNTdmMgcnVubmluZyBhbmQgYXQgbGVhc3Qgb25lIFdpbmRvd3MgRmlyZXdhbGwgcHJvZmlsZSBlbmFibGVkJyAtQWN0dWFsICJTZXJ2aWNlPSQoJGZpcmV3YWxsRW5naW5lLnNlcnZpY2VTdGF0ZSk7RW5hYmxlZD0kKCRmaXJld2FsbEVuZ2luZS5lbmFibGVkUHJvZmlsZXMgLWpvaW4gJywnKTtEaXNhYmxlZD0kKCRmaXJld2FsbEVuZ2luZS5kaXNhYmxlZFByb2ZpbGVzIC1qb2luICcsJykiCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0ZJUkVXQUxMX0VOR0lORScgLVN0YXR1cyAnTk9UX0FQUExJQ0FCTEUnIC1FeHBlY3RlZCAnTG9ja2Rvd24gb3BlcmF0aW9uJyAtQWN0dWFsICdSZXN0b3JlIGRvZXMgbm90IGNoYW5nZSBmaXJld2FsbCBlbmdpbmUgc3RhdGUnCiAgICAgICAgfQogICAgICAgICRmaXJld2FsbFJlc3VsdD1UZXN0LUZpcmV3YWxsUGxhbiAtUGxhbiAkcGxhbiAtT3BlcmF0aW9uICRvcGVyYXRpb24KICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnQVVUSF9GSVJFV0FMTCcgLVN0YXR1cyAkKGlmKCRmaXJld2FsbFJlc3VsdC5QYXNzKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJChpZigkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKXsnQWxsIHBsYW5uZWQgZW1lcmdlbmN5IGJsb2NrIHJ1bGVzIGFjdGl2ZSd9ZWxzZXsnQWxsIGVtZXJnZW5jeSBibG9jayBydWxlcyBhYnNlbnQnfSkgLUFjdHVhbCAkKGlmKCRmaXJld2FsbFJlc3VsdC5GYWlsdXJlcy5Db3VudCAtZXEgMCl7JGZpcmV3YWxsUmVzdWx0LkFjdHVhbH1lbHNleyRmaXJld2FsbFJlc3VsdC5GYWlsdXJlcyAtam9pbiAnIHwgJ30pCgogICAgICAgICRuZXRsb2dvbj1HZXQtQ2ltSW5zdGFuY2UgV2luMzJfU2VydmljZSAtRmlsdGVyICJOYW1lPSdOZXRsb2dvbiciIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgaWYoJG9wZXJhdGlvbiAtZXEgJ1JFU1RPUkUnKSB7CiAgICAgICAgICAgICRvcmlnaW5hbE5ldGxvZ29uPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ29yaWdpbmFsU3RhdGUnICRudWxsKSAnbmV0bG9nb24nICRudWxsCiAgICAgICAgICAgICRleHBlY3RlZFN0YXJ0PU5vcm1hbGl6ZS1TdGFydE1vZGUgKFtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRvcmlnaW5hbE5ldGxvZ29uICdzdGFydE1vZGUnICdNYW51YWwnKSkKICAgICAgICAgICAgJGV4cGVjdGVkU3RhdGU9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJG9yaWdpbmFsTmV0bG9nb24gJ3N0YXRlJyAnU3RvcHBlZCcpCiAgICAgICAgICAgIGlmKCRleHBlY3RlZFN0YXRlIC1lcSAnUnVubmluZycgLWFuZCAkbmV0bG9nb24uU3RhdGUgLW5lICdSdW5uaW5nJyAtYW5kICRleHBlY3RlZFN0YXJ0IC1uZSAnRGlzYWJsZWQnKSB7CiAgICAgICAgICAgICAgICB0cnkgeyBTdGFydC1TZXJ2aWNlIE5ldGxvZ29uIC1FcnJvckFjdGlvbiBTdG9wOyBTdGFydC1TbGVlcCAtU2Vjb25kcyAyIH0gY2F0Y2ggeyB9CiAgICAgICAgICAgICAgICAkbmV0bG9nb249R2V0LUNpbUluc3RhbmNlIFdpbjMyX1NlcnZpY2UgLUZpbHRlciAiTmFtZT0nTmV0bG9nb24nIiAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgICAgICB9CiAgICAgICAgICAgICRuZXRQYXNzPSgoTm9ybWFsaXplLVN0YXJ0TW9kZSAoW3N0cmluZ10kbmV0bG9nb24uU3RhcnRNb2RlKSkgLWVxICRleHBlY3RlZFN0YXJ0IC1hbmQgKCgkZXhwZWN0ZWRTdGF0ZSAtbmUgJ1J1bm5pbmcnKSAtb3IgJG5ldGxvZ29uLlN0YXRlIC1lcSAnUnVubmluZycpKQogICAgICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnTkVUTE9HT04nIC1TdGF0dXMgJChpZigkbmV0UGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICIkZXhwZWN0ZWRTdGF0ZS8kZXhwZWN0ZWRTdGFydCIgLUFjdHVhbCAiJCgkbmV0bG9nb24uU3RhdGUpLyQoTm9ybWFsaXplLVN0YXJ0TW9kZSAoW3N0cmluZ10kbmV0bG9nb24uU3RhcnRNb2RlKSkiCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJG5ldFBhc3M9KCRuZXRsb2dvbi5TdGF0ZSAtZXEgJ1N0b3BwZWQnIC1hbmQgKE5vcm1hbGl6ZS1TdGFydE1vZGUgKFtzdHJpbmddJG5ldGxvZ29uLlN0YXJ0TW9kZSkpIC1lcSAnRGlzYWJsZWQnKQogICAgICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnTkVUTE9HT04nIC1TdGF0dXMgJChpZigkbmV0UGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICdTdG9wcGVkL0Rpc2FibGVkJyAtQWN0dWFsICIkKCRuZXRsb2dvbi5TdGF0ZSkvJChOb3JtYWxpemUtU3RhcnRNb2RlIChbc3RyaW5nXSRuZXRsb2dvbi5TdGFydE1vZGUpKSIKICAgICAgICB9CgogICAgICAgICRjdXJyZW50S2RjPUdldC1LZGNQcm94eVN0YXRlCiAgICAgICAgaWYoJG9wZXJhdGlvbiAtZXEgJ0xPQ0tET1dOJykgewogICAgICAgICAgICAka2RjUGFzcz0oJGN1cnJlbnRLZGMuZW5hYmxlZEV4aXN0cyAtYW5kIFtpbnRdJGN1cnJlbnRLZGMuZW5hYmxlZCAtZXEgMCAtYW5kIC1ub3QgJGN1cnJlbnRLZGMubWFwcGluZ0V4aXN0cykKICAgICAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0tEQ19QUk9YWScgLVN0YXR1cyAkKGlmKCRrZGNQYXNzKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ0Rpc2FibGVkIGFuZCBtYXBwaW5ncyBhYnNlbnQnIC1BY3R1YWwgIkVuYWJsZWQ9JCgkY3VycmVudEtkYy5lbmFibGVkKTtNYXBwaW5nRXhpc3RzPSQoJGN1cnJlbnRLZGMubWFwcGluZ0V4aXN0cykiCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJG9yaWdpbmFsS2RjPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ29yaWdpbmFsU3RhdGUnICRudWxsKSAna2RjUHJveHknICRudWxsCiAgICAgICAgICAgICRrZGNQYXNzPUNvbXBhcmUtS2RjUHJveHlSZXN0b3JlIC1PcmlnaW5hbCAkb3JpZ2luYWxLZGMgLUN1cnJlbnQgJGN1cnJlbnRLZGMKICAgICAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0tEQ19QUk9YWScgLVN0YXR1cyAkKGlmKCRrZGNQYXNzKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ09yaWdpbmFsIEtEQyBwcm94eSBjb25maWd1cmF0aW9uJyAtQWN0dWFsICJFbmFibGVkRXhpc3RzPSQoJGN1cnJlbnRLZGMuZW5hYmxlZEV4aXN0cyk7RW5hYmxlZD0kKCRjdXJyZW50S2RjLmVuYWJsZWQpO01hcHBpbmdFeGlzdHM9JCgkY3VycmVudEtkYy5tYXBwaW5nRXhpc3RzKSIKICAgICAgICB9CiAgICB9IGVsc2UgewogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdBVVRIX0ZJUkVXQUxMJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgc2NvcGUgb24gYSBkb21haW4tam9pbmVkIGRldmljZScgLUFjdHVhbCAnTm90IGNvbmZpZ3VyZWQnCiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ05FVExPR09OJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgc2NvcGUgb24gYSBkb21haW4tam9pbmVkIGRldmljZScgLUFjdHVhbCAnTm90IGNoYW5nZWQnCiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0tEQ19QUk9YWScgLVN0YXR1cyAnTk9UX0FQUExJQ0FCTEUnIC1FeHBlY3RlZCAnQUxMIHNjb3BlIG9uIGEgZG9tYWluLWpvaW5lZCBkZXZpY2UnIC1BY3R1YWwgJ05vdCBjaGFuZ2VkJwogICAgfQoKICAgICRmYWlsZWQ9QCgkY2hlY2tzIHwgV2hlcmUtT2JqZWN0IHskXy5zdGF0dXMgLWVxICdGQUlMJ30pCiAgICAkd2FybmluZ0NoZWNrcz1AKCRjaGVja3MgfCBXaGVyZS1PYmplY3QgeyRfLnN0YXR1cyAtZXEgJ1dBUk5JTkcnfSkKICAgICRvdmVyYWxsPWlmKCRmYWlsZWQuQ291bnQgLWd0IDApeydGQUlMRURfVkVSSUZJQ0FUSU9OJ31lbHNlaWYoJHdhcm5pbmdDaGVja3MuQ291bnQgLWd0IDApeydTVUNDRVNTX1dJVEhfV0FSTklOR1MnfWVsc2V7J1NVQ0NFU1MnfQogICAgJHZlcmlmaWNhdGlvbj1bcHNjdXN0b21vYmplY3RdW29yZGVyZWRdQHsKICAgICAgICBvcGVyYXRpb249JG9wZXJhdGlvbjtzdGF0dXM9J0NPTVBMRVRFRCc7b3ZlcmFsbFN0YXR1cz0kb3ZlcmFsbAogICAgICAgIHNjaGVkdWxlZFV0Yz1HZXQtUHJvcGVydHlWYWx1ZSAkcGVuZGluZyAnc2NoZWR1bGVkVXRjJyAkbnVsbDt2ZXJpZmllZFV0Yz1HZXQtVXRjVGltZXN0YW1wCiAgICAgICAgY3VycmVudEJvb3RUaW1lPSRjdXJyZW50Qm9vdC5Ub1N0cmluZygnbycpO3JlYm9vdFZlcmlmaWVkPSRyZWJvb3RQYXNzCiAgICAgICAgcGFzc2VkQ2hlY2tDb3VudD1AKCRjaGVja3MgfCBXaGVyZS1PYmplY3QgeyRfLnN0YXR1cyAtZXEgJ1BBU1MnfSkuQ291bnQKICAgICAgICB3YXJuaW5nQ2hlY2tDb3VudD0kd2FybmluZ0NoZWNrcy5Db3VudDt3YXJuaW5nQ2hlY2tzPSgoJHdhcm5pbmdDaGVja3MgfCBGb3JFYWNoLU9iamVjdCB7JF8ubmFtZX0pIC1qb2luICc7JykKICAgICAgICBmYWlsZWRDaGVja0NvdW50PSRmYWlsZWQuQ291bnQ7ZmFpbGVkQ2hlY2tzPSgoJGZhaWxlZCB8IEZvckVhY2gtT2JqZWN0IHskXy5uYW1lfSkgLWpvaW4gJzsnKQogICAgICAgIHRhc2tOYW1lPSRUYXNrTmFtZTtzY3JpcHRQYXRoPSRQU0NvbW1hbmRQYXRoO2NoZWNrcz0kY2hlY2tzCiAgICB9CiAgICBpZigkc3RhdGUuUFNPYmplY3QuUHJvcGVydGllcy5OYW1lIC1jb250YWlucyAncG9zdEJvb3RWZXJpZmljYXRpb24nKXskc3RhdGUucG9zdEJvb3RWZXJpZmljYXRpb249JHZlcmlmaWNhdGlvbn1lbHNleyRzdGF0ZXxBZGQtTWVtYmVyIC1Ob3RlUHJvcGVydHlOYW1lIHBvc3RCb290VmVyaWZpY2F0aW9uIC1Ob3RlUHJvcGVydHlWYWx1ZSAkdmVyaWZpY2F0aW9ufQogICAgaWYoJG9wZXJhdGlvbiAtZXEgJ0xPQ0tET1dOJyl7CiAgICAgICAgJHN0YXRlLnN0YXR1cz1pZigkb3ZlcmFsbCAtZXEgJ1NVQ0NFU1MnKXsnQUNUSVZFX1ZFUklGSUVEJ31lbHNlaWYoJG92ZXJhbGwgLWVxICdTVUNDRVNTX1dJVEhfV0FSTklOR1MnKXsnQUNUSVZFX1ZFUklGSUVEX1dJVEhfV0FSTklOR1MnfWVsc2V7J0xPQ0tET1dOX1BBUlRJQUwnfQogICAgfQogICAgaWYoJG9wZXJhdGlvbiAtZXEgJ1JFU1RPUkUnKXsKICAgICAgICAkc3RhdGUuc3RhdHVzPWlmKCRvdmVyYWxsIC1lcSAnU1VDQ0VTUycpeydSRVNUT1JFRF9WRVJJRklFRCd9ZWxzZWlmKCRvdmVyYWxsIC1lcSAnU1VDQ0VTU19XSVRIX1dBUk5JTkdTJyl7J1JFU1RPUkVEX1ZFUklGSUVEX1dJVEhfV0FSTklOR1MnfWVsc2V7J1JFU1RPUkVfUEFSVElBTCd9CiAgICB9CiAgICBXcml0ZS1TdGF0ZSAtU3RhdGUgJHN0YXRlCiAgICAkc2NyaXB0OlZlcmlmaWNhdGlvblJlY29yZGVkPSR0cnVlCn0KY2F0Y2ggewogICAgdHJ5IHsKICAgICAgICBpZihUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRTdGF0ZVBhdGgpIHsKICAgICAgICAgICAgJHN0YXRlPUdldC1Db250ZW50IC1MaXRlcmFsUGF0aCAkU3RhdGVQYXRoIC1SYXcgLUVycm9yQWN0aW9uIFN0b3AgfCBDb252ZXJ0RnJvbS1Kc29uIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgICAgICRwZW5kaW5nPUdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAncG9zdEJvb3RWZXJpZmljYXRpb24nICRudWxsCiAgICAgICAgICAgICRmYWlsdXJlPVtwc2N1c3RvbW9iamVjdF1bb3JkZXJlZF1Ae29wZXJhdGlvbj1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkcGVuZGluZyAnb3BlcmF0aW9uJyAnVU5LTk9XTicpO3N0YXR1cz0nRkFJTEVEJztvdmVyYWxsU3RhdHVzPSdGQUlMRURfVkVSSUZJQ0FUSU9OJzt2ZXJpZmllZFV0Yz1HZXQtVXRjVGltZXN0YW1wO3JlYm9vdFZlcmlmaWVkPSRmYWxzZTtwYXNzZWRDaGVja0NvdW50PTA7d2FybmluZ0NoZWNrQ291bnQ9MDt3YXJuaW5nQ2hlY2tzPScnO2ZhaWxlZENoZWNrQ291bnQ9MTtmYWlsZWRDaGVja3M9J1BPU1RCT09UX1ZFUklGSUVSX0VYQ0VQVElPTic7ZXJyb3JNZXNzYWdlPSRfLkV4Y2VwdGlvbi5NZXNzYWdlO3Rhc2tOYW1lPSRUYXNrTmFtZTtzY3JpcHRQYXRoPSRQU0NvbW1hbmRQYXRoO2NoZWNrcz1AKCl9CiAgICAgICAgICAgIGlmKCRzdGF0ZS5QU09iamVjdC5Qcm9wZXJ0aWVzLk5hbWUgLWNvbnRhaW5zICdwb3N0Qm9vdFZlcmlmaWNhdGlvbicpeyRzdGF0ZS5wb3N0Qm9vdFZlcmlmaWNhdGlvbj0kZmFpbHVyZX1lbHNleyRzdGF0ZXxBZGQtTWVtYmVyIC1Ob3RlUHJvcGVydHlOYW1lIHBvc3RCb290VmVyaWZpY2F0aW9uIC1Ob3RlUHJvcGVydHlWYWx1ZSAkZmFpbHVyZX0KICAgICAgICAgICAgaWYoJGZhaWx1cmUub3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKXskc3RhdGUuc3RhdHVzPSdMT0NLRE9XTl9QQVJUSUFMJ30KICAgICAgICAgICAgaWYoJGZhaWx1cmUub3BlcmF0aW9uIC1lcSAnUkVTVE9SRScpeyRzdGF0ZS5zdGF0dXM9J1JFU1RPUkVfUEFSVElBTCd9CiAgICAgICAgICAgIFdyaXRlLVN0YXRlIC1TdGF0ZSAkc3RhdGUKICAgICAgICAgICAgJHNjcmlwdDpWZXJpZmljYXRpb25SZWNvcmRlZD0kdHJ1ZQogICAgICAgIH0KICAgIH0gY2F0Y2ggeyB9Cn0KZmluYWxseSB7CiAgICBpZigkc2NyaXB0OlZlcmlmaWNhdGlvblJlY29yZGVkKSB7CiAgICAgICAgdHJ5IHsgJiAiJGVudjpTeXN0ZW1Sb290XFN5c3RlbTMyXHNjaHRhc2tzLmV4ZSIgL0RlbGV0ZSAvVE4gJFRhc2tOYW1lIC9GIHwgT3V0LU51bGwgfSBjYXRjaCB7IH0KICAgICAgICB0cnkgeyBSZW1vdmUtSXRlbSAtTGl0ZXJhbFBhdGggJFBTQ29tbWFuZFBhdGggLUZvcmNlIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIH0gY2F0Y2ggeyB9CiAgICB9Cn0K'
$DisruptionWorkerBase64 = 'PCMKLlNZTk9QU0lTCiAgICBJbnRlcm5hbCBkaXNydXB0aW9uIHdvcmtlciBmb3IgRW1lcmdlbmN5IExvY2tkb3duIFYxLjQuMC4KLkRFU0NSSVBUSU9OCiAgICBHZW5lcmF0ZWQgYW5kIHN0YXJ0ZWQgYnkgdGhlIG9wZXJhdG9yLWZhY2luZyBMb2NrZG93biBzY3JpcHQuCiAgICBJdCBydW5zIGluZGVwZW5kZW50bHkgYXMgU1lTVEVNIGluIFRhc2sgU2NoZWR1bGVyIHNvIHRoYXQgbG9nZ2luZyBvZmYgdGhlCiAgICBpbnRlcmFjdGl2ZSBzZXNzaW9uIGNhbm5vdCB0ZXJtaW5hdGUgdGhlIEVuZHBvaW50IENlbnRyYWwgc2NyaXB0IGJlZm9yZQogICAgUmVtYXJrcyBhbmQgc3RhdGUgYXJlIHJldHVybmVkLiBJdCBzY2hlZHVsZXMgdGhlIG1hbmRhdG9yeSByZWJvb3QsIGxvZ3Mgb2ZmCiAgICB0YXJnZXRlZCBzZXNzaW9ucywgcmVjb3JkcyBlZmZlY3RpdmUgcmVzdWx0cywgYW5kIHJlbW92ZXMgaXRzIG93biB0YXNrLgogICAgVGhpcyBpcyBub3QgYW4gb3BlcmF0b3ItZmFjaW5nIEVuZHBvaW50IENlbnRyYWwgc2NyaXB0LgojPgpwYXJhbSgpClNldC1TdHJpY3RNb2RlIC1WZXJzaW9uIDIuMAokRXJyb3JBY3Rpb25QcmVmZXJlbmNlID0gJ1N0b3AnCiRQcm9ncmVzc1ByZWZlcmVuY2UgPSAnU2lsZW50bHlDb250aW51ZScKCiRCYXNlRGlyZWN0b3J5ID0gSm9pbi1QYXRoICRlbnY6UHJvZ3JhbURhdGEgJ0VtZXJnZW5jeUxvY2tkb3duJwokU3RhdGVQYXRoID0gSm9pbi1QYXRoICRCYXNlRGlyZWN0b3J5ICdFbWVyZ2VuY3lMb2NrZG93bi5qc29uJwokVGVtcG9yYXJ5U3RhdGVQYXRoID0gSm9pbi1QYXRoICRCYXNlRGlyZWN0b3J5ICdFbWVyZ2VuY3lMb2NrZG93bi50bXAnCiRUYXNrTmFtZSA9ICdFbWVyZ2VuY3lMb2NrZG93bi1EaXNydXB0aW9uV29ya2VyLXYxNDAnCiRzY3JpcHQ6U3RhdGUgPSAkbnVsbApTdGFydC1TbGVlcCAtU2Vjb25kcyA4CgpmdW5jdGlvbiBHZXQtVXRjVGltZXN0YW1wIHsgW0RhdGVUaW1lXTo6VXRjTm93LlRvU3RyaW5nKCdvJykgfQpmdW5jdGlvbiBTZXQtU2VjdXJlRGlyZWN0b3J5QWNsIHsKICAgIHBhcmFtKFtzdHJpbmddJFBhdGgpCiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkUGF0aCkpIHsgJG51bGwgPSBOZXctSXRlbSAtUGF0aCAkUGF0aCAtSXRlbVR5cGUgRGlyZWN0b3J5IC1Gb3JjZSB9CiAgICAkc3lzdGVtU2lkID0gW1N5c3RlbS5TZWN1cml0eS5QcmluY2lwYWwuU2VjdXJpdHlJZGVudGlmaWVyXTo6bmV3KCdTLTEtNS0xOCcpCiAgICAkYWRtaW5pc3RyYXRvcnNTaWQgPSBbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5TZWN1cml0eUlkZW50aWZpZXJdOjpuZXcoJ1MtMS01LTMyLTU0NCcpCiAgICAkaW5oZXJpdGFuY2UgPSBbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuSW5oZXJpdGFuY2VGbGFnc106OkNvbnRhaW5lckluaGVyaXQgLWJvciBbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuSW5oZXJpdGFuY2VGbGFnc106Ok9iamVjdEluaGVyaXQKICAgICRzZWN1cml0eSA9IFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5EaXJlY3RvcnlTZWN1cml0eV06Om5ldygpCiAgICAkc2VjdXJpdHkuU2V0QWNjZXNzUnVsZVByb3RlY3Rpb24oJHRydWUsJGZhbHNlKQogICAgJHNlY3VyaXR5LlNldE93bmVyKCRzeXN0ZW1TaWQpCiAgICAkc2VjdXJpdHkuQWRkQWNjZXNzUnVsZShbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbUFjY2Vzc1J1bGVdOjpuZXcoJHN5c3RlbVNpZCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbVJpZ2h0c106OkZ1bGxDb250cm9sLCRpbmhlcml0YW5jZSxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuUHJvcGFnYXRpb25GbGFnc106Ok5vbmUsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkFjY2Vzc0NvbnRyb2xUeXBlXTo6QWxsb3cpKQogICAgJHNlY3VyaXR5LkFkZEFjY2Vzc1J1bGUoW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1BY2Nlc3NSdWxlXTo6bmV3KCRhZG1pbmlzdHJhdG9yc1NpZCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbVJpZ2h0c106OkZ1bGxDb250cm9sLCRpbmhlcml0YW5jZSxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuUHJvcGFnYXRpb25GbGFnc106Ok5vbmUsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkFjY2Vzc0NvbnRyb2xUeXBlXTo6QWxsb3cpKQogICAgW1N5c3RlbS5JTy5EaXJlY3RvcnlJbmZvXTo6bmV3KCRQYXRoKS5TZXRBY2Nlc3NDb250cm9sKCRzZWN1cml0eSkKfQpmdW5jdGlvbiBTZXQtU2VjdXJlRmlsZUFjbCB7CiAgICBwYXJhbShbc3RyaW5nXSRQYXRoKQogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFBhdGgpKSB7IHJldHVybiB9CiAgICAkc3lzdGVtU2lkID0gW1N5c3RlbS5TZWN1cml0eS5QcmluY2lwYWwuU2VjdXJpdHlJZGVudGlmaWVyXTo6bmV3KCdTLTEtNS0xOCcpCiAgICAkYWRtaW5pc3RyYXRvcnNTaWQgPSBbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5TZWN1cml0eUlkZW50aWZpZXJdOjpuZXcoJ1MtMS01LTMyLTU0NCcpCiAgICAkc2VjdXJpdHkgPSBbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVNlY3VyaXR5XTo6bmV3KCkKICAgICRzZWN1cml0eS5TZXRBY2Nlc3NSdWxlUHJvdGVjdGlvbigkdHJ1ZSwkZmFsc2UpCiAgICAkc2VjdXJpdHkuU2V0T3duZXIoJHN5c3RlbVNpZCkKICAgICRzZWN1cml0eS5BZGRBY2Nlc3NSdWxlKFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5GaWxlU3lzdGVtQWNjZXNzUnVsZV06Om5ldygkc3lzdGVtU2lkLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5GaWxlU3lzdGVtUmlnaHRzXTo6RnVsbENvbnRyb2wsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkFjY2Vzc0NvbnRyb2xUeXBlXTo6QWxsb3cpKQogICAgJHNlY3VyaXR5LkFkZEFjY2Vzc1J1bGUoW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1BY2Nlc3NSdWxlXTo6bmV3KCRhZG1pbmlzdHJhdG9yc1NpZCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbVJpZ2h0c106OkZ1bGxDb250cm9sLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgIFtTeXN0ZW0uSU8uRmlsZUluZm9dOjpuZXcoJFBhdGgpLlNldEFjY2Vzc0NvbnRyb2woJHNlY3VyaXR5KQp9CmZ1bmN0aW9uIFdyaXRlLVN0YXRlIHsKICAgIFNldC1TZWN1cmVEaXJlY3RvcnlBY2wgLVBhdGggJEJhc2VEaXJlY3RvcnkKICAgICRqc29uID0gJHNjcmlwdDpTdGF0ZSB8IENvbnZlcnRUby1Kc29uIC1EZXB0aCAyNAogICAgW1N5c3RlbS5JTy5GaWxlXTo6V3JpdGVBbGxUZXh0KCRUZW1wb3JhcnlTdGF0ZVBhdGgsJGpzb24sW1N5c3RlbS5UZXh0LlVURjhFbmNvZGluZ106Om5ldygkZmFsc2UpKQogICAgU2V0LVNlY3VyZUZpbGVBY2wgLVBhdGggJFRlbXBvcmFyeVN0YXRlUGF0aAogICAgTW92ZS1JdGVtIC1MaXRlcmFsUGF0aCAkVGVtcG9yYXJ5U3RhdGVQYXRoIC1EZXN0aW5hdGlvbiAkU3RhdGVQYXRoIC1Gb3JjZQogICAgU2V0LVNlY3VyZUZpbGVBY2wgLVBhdGggJFN0YXRlUGF0aAp9CmZ1bmN0aW9uIEFkZC1Xb3JrZXJTdGFnZSB7CiAgICBwYXJhbSgKICAgICAgICBbc3RyaW5nXSROYW1lLAogICAgICAgIFtWYWxpZGF0ZVNldCgnU1VDQ0VTUycsJ1dBUk5JTkcnLCdGQUlMRUQnKV1bc3RyaW5nXSRTdGF0dXMsCiAgICAgICAgW2RhdGV0aW1lXSRTdGFydGVkLAogICAgICAgIFtpbnRdJEFmZmVjdGVkQ291bnQgPSAwLAogICAgICAgIFtzdHJpbmddJEVycm9yQ29kZSA9ICRudWxsLAogICAgICAgIFtzdHJpbmddJEVycm9yTWVzc2FnZSA9ICRudWxsLAogICAgICAgIFtzdHJpbmddJEV4cGVjdGVkID0gJG51bGwsCiAgICAgICAgW3N0cmluZ10kQWN0dWFsID0gJG51bGwsCiAgICAgICAgW1ZhbGlkYXRlU2V0KCdQQVNTJywnV0FSTklORycsJ0ZBSUwnLCdQRU5ESU5HJyldW3N0cmluZ10kVmVyaWZpY2F0aW9uID0gJ1BBU1MnCiAgICApCiAgICBpZiAoJG51bGwgLWVxICRTdGFydGVkKSB7ICRTdGFydGVkID0gW0RhdGVUaW1lXTo6VXRjTm93IH0KICAgICRjb21wbGV0ZWQgPSBbRGF0ZVRpbWVdOjpVdGNOb3cKICAgICRzY3JpcHQ6U3RhdGUuc3RhZ2VzICs9IFtwc2N1c3RvbW9iamVjdF1bb3JkZXJlZF1AewogICAgICAgIG5hbWU9JE5hbWU7c3RhdHVzPSRTdGF0dXM7dmVyaWZpY2F0aW9uPSRWZXJpZmljYXRpb24KICAgICAgICBzdGFydGVkVXRjPSRTdGFydGVkLlRvU3RyaW5nKCdvJyk7Y29tcGxldGVkVXRjPSRjb21wbGV0ZWQuVG9TdHJpbmcoJ28nKQogICAgICAgIGR1cmF0aW9uTXM9W2ludF1bTWF0aF06Ok1heCgwLCgkY29tcGxldGVkLSRTdGFydGVkKS5Ub3RhbE1pbGxpc2Vjb25kcykKICAgICAgICBhZmZlY3RlZENvdW50PSRBZmZlY3RlZENvdW50O2V4cGVjdGVkPSRFeHBlY3RlZDthY3R1YWw9JEFjdHVhbAogICAgICAgIGVycm9yQ29kZT0kRXJyb3JDb2RlO2Vycm9yTWVzc2FnZT0kRXJyb3JNZXNzYWdlO21lc3NhZ2U9J1JlY29yZGVkIGJ5IHRoZSBpbmRlcGVuZGVudCBkaXNydXB0aW9uIHdvcmtlci4nCiAgICB9CiAgICBpZiAoJFN0YXR1cyAtZXEgJ1dBUk5JTkcnIC1hbmQgJEVycm9yQ29kZSkgewogICAgICAgICRzY3JpcHQ6U3RhdGUud2FybmluZ3MgKz0gW3BzY3VzdG9tb2JqZWN0XVtvcmRlcmVkXUB7Y29kZT0kRXJyb3JDb2RlO21lc3NhZ2U9JEVycm9yTWVzc2FnZTt0aW1lc3RhbXBVdGM9R2V0LVV0Y1RpbWVzdGFtcH0KICAgIH0KICAgIGVsc2VpZiAoJFN0YXR1cyAtZXEgJ0ZBSUxFRCcgLWFuZCAkRXJyb3JDb2RlKSB7CiAgICAgICAgJHNjcmlwdDpTdGF0ZS5lcnJvcnMgKz0gW3BzY3VzdG9tb2JqZWN0XVtvcmRlcmVkXUB7Y29kZT0kRXJyb3JDb2RlO21lc3NhZ2U9JEVycm9yTWVzc2FnZTtzdGFnZT0kTmFtZTt0aW1lc3RhbXBVdGM9R2V0LVV0Y1RpbWVzdGFtcH0KICAgIH0KICAgIFdyaXRlLVN0YXRlCn0KZnVuY3Rpb24gSW5pdGlhbGl6ZS1XdHNOYXRpdmVUeXBlIHsKICAgIGlmICgnRW1lcmdlbmN5U2VjdXJpdHkuRGlzcnVwdGlvbk5hdGl2ZVYxNDAnIC1hcyBbdHlwZV0pIHsgcmV0dXJuIH0KICAgICRzb3VyY2UgPSBAJwp1c2luZyBTeXN0ZW07CnVzaW5nIFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljOwp1c2luZyBTeXN0ZW0uQ29tcG9uZW50TW9kZWw7CnVzaW5nIFN5c3RlbS5SdW50aW1lLkludGVyb3BTZXJ2aWNlczsKbmFtZXNwYWNlIEVtZXJnZW5jeVNlY3VyaXR5CnsKICAgIHB1YmxpYyBzdGF0aWMgY2xhc3MgRGlzcnVwdGlvbk5hdGl2ZVYxNDAKICAgIHsKICAgICAgICBbU3RydWN0TGF5b3V0KExheW91dEtpbmQuU2VxdWVudGlhbCwgQ2hhclNldD1DaGFyU2V0LlVuaWNvZGUpXQogICAgICAgIHByaXZhdGUgc3RydWN0IFdUU19TRVNTSU9OX0lORk8geyBwdWJsaWMgaW50IFNlc3Npb25JRDsgcHVibGljIHN0cmluZyBwV2luU3RhdGlvbk5hbWU7IHB1YmxpYyBpbnQgU3RhdGU7IH0KICAgICAgICBwcml2YXRlIGVudW0gV1RTX0lORk9fQ0xBU1MgeyBXVFNJbml0aWFsUHJvZ3JhbSwgV1RTQXBwbGljYXRpb25OYW1lLCBXVFNXb3JraW5nRGlyZWN0b3J5LCBXVFNPRU1JZCwgV1RTU2Vzc2lvbklkLCBXVFNVc2VyTmFtZSwgV1RTV2luU3RhdGlvbk5hbWUsIFdUU0RvbWFpbk5hbWUgfQogICAgICAgIFtEbGxJbXBvcnQoInd0c2FwaTMyLmRsbCIsIFNldExhc3RFcnJvcj10cnVlKV0gcHJpdmF0ZSBzdGF0aWMgZXh0ZXJuIGJvb2wgV1RTRW51bWVyYXRlU2Vzc2lvbnMoSW50UHRyIGhTZXJ2ZXIsIGludCBSZXNlcnZlZCwgaW50IFZlcnNpb24sIG91dCBJbnRQdHIgcHBTZXNzaW9uSW5mbywgb3V0IGludCBwQ291bnQpOwogICAgICAgIFtEbGxJbXBvcnQoInd0c2FwaTMyLmRsbCIpXSBwcml2YXRlIHN0YXRpYyBleHRlcm4gdm9pZCBXVFNGcmVlTWVtb3J5KEludFB0ciBwTWVtb3J5KTsKICAgICAgICBbRGxsSW1wb3J0KCJ3dHNhcGkzMi5kbGwiLCBDaGFyU2V0PUNoYXJTZXQuVW5pY29kZSwgU2V0TGFzdEVycm9yPXRydWUpXSBwcml2YXRlIHN0YXRpYyBleHRlcm4gYm9vbCBXVFNRdWVyeVNlc3Npb25JbmZvcm1hdGlvbihJbnRQdHIgaFNlcnZlciwgaW50IHNlc3Npb25JZCwgV1RTX0lORk9fQ0xBU1MgaW5mb0NsYXNzLCBvdXQgSW50UHRyIHBwQnVmZmVyLCBvdXQgaW50IHBCeXRlc1JldHVybmVkKTsKICAgICAgICBbRGxsSW1wb3J0KCJ3dHNhcGkzMi5kbGwiLCBTZXRMYXN0RXJyb3I9dHJ1ZSldIHByaXZhdGUgc3RhdGljIGV4dGVybiBib29sIFdUU0xvZ29mZlNlc3Npb24oSW50UHRyIGhTZXJ2ZXIsIGludCBzZXNzaW9uSWQsIGJvb2wgd2FpdCk7CiAgICAgICAgcHJpdmF0ZSBzdGF0aWMgc3RyaW5nIFF1ZXJ5KGludCBzZXNzaW9uSWQsIFdUU19JTkZPX0NMQVNTIGluZm9DbGFzcykgeyBJbnRQdHIgcD1JbnRQdHIuWmVybzsgaW50IGJ5dGVzPTA7IHRyeSB7IGlmKCFXVFNRdWVyeVNlc3Npb25JbmZvcm1hdGlvbihJbnRQdHIuWmVybyxzZXNzaW9uSWQsaW5mb0NsYXNzLG91dCBwLG91dCBieXRlcykpIHJldHVybiAiIjsgcmV0dXJuIHA9PUludFB0ci5aZXJvID8gIiIgOiBNYXJzaGFsLlB0clRvU3RyaW5nVW5pKHApOyB9IGZpbmFsbHkgeyBpZihwIT1JbnRQdHIuWmVybykgV1RTRnJlZU1lbW9yeShwKTsgfSB9CiAgICAgICAgcHVibGljIHN0YXRpYyBzdHJpbmdbXSBHZXRTZXNzaW9ucygpIHsgSW50UHRyIGJ1ZmZlcj1JbnRQdHIuWmVybzsgaW50IGNvdW50PTA7IExpc3Q8c3RyaW5nPiByZXN1bHQ9bmV3IExpc3Q8c3RyaW5nPigpOyB0cnkgeyBpZighV1RTRW51bWVyYXRlU2Vzc2lvbnMoSW50UHRyLlplcm8sMCwxLG91dCBidWZmZXIsb3V0IGNvdW50KSkgdGhyb3cgbmV3IFdpbjMyRXhjZXB0aW9uKE1hcnNoYWwuR2V0TGFzdFdpbjMyRXJyb3IoKSk7IGludCBzaXplPU1hcnNoYWwuU2l6ZU9mKHR5cGVvZihXVFNfU0VTU0lPTl9JTkZPKSk7IGZvcihpbnQgaT0wO2k8Y291bnQ7aSsrKXsgV1RTX1NFU1NJT05fSU5GTyBpdGVtPShXVFNfU0VTU0lPTl9JTkZPKU1hcnNoYWwuUHRyVG9TdHJ1Y3R1cmUoSW50UHRyLkFkZChidWZmZXIsaSpzaXplKSx0eXBlb2YoV1RTX1NFU1NJT05fSU5GTykpOyBzdHJpbmcgdXNlcj1RdWVyeShpdGVtLlNlc3Npb25JRCxXVFNfSU5GT19DTEFTUy5XVFNVc2VyTmFtZSk7IHN0cmluZyBkb21haW49UXVlcnkoaXRlbS5TZXNzaW9uSUQsV1RTX0lORk9fQ0xBU1MuV1RTRG9tYWluTmFtZSk7IHJlc3VsdC5BZGQoaXRlbS5TZXNzaW9uSUQrInwiK2RvbWFpbisifCIrdXNlcisifCIraXRlbS5TdGF0ZSk7IH0gcmV0dXJuIHJlc3VsdC5Ub0FycmF5KCk7IH0gZmluYWxseSB7IGlmKGJ1ZmZlciE9SW50UHRyLlplcm8pIFdUU0ZyZWVNZW1vcnkoYnVmZmVyKTsgfSB9CiAgICAgICAgcHVibGljIHN0YXRpYyB2b2lkIExvZ29mZihpbnQgc2Vzc2lvbklkKSB7IGlmKCFXVFNMb2dvZmZTZXNzaW9uKEludFB0ci5aZXJvLHNlc3Npb25JZCxmYWxzZSkpIHRocm93IG5ldyBXaW4zMkV4Y2VwdGlvbihNYXJzaGFsLkdldExhc3RXaW4zMkVycm9yKCkpOyB9CiAgICB9Cn0KJ0AKICAgIEFkZC1UeXBlIC1UeXBlRGVmaW5pdGlvbiAkc291cmNlIC1MYW5ndWFnZSBDU2hhcnAgLUVycm9yQWN0aW9uIFN0b3AgfCBPdXQtTnVsbAp9CmZ1bmN0aW9uIEdldC1XdHNTZXNzaW9ucyB7CiAgICAkaXRlbXMgPSBAKCkKICAgIGZvcmVhY2ggKCRsaW5lIGluIFtFbWVyZ2VuY3lTZWN1cml0eS5EaXNydXB0aW9uTmF0aXZlVjE0MF06OkdldFNlc3Npb25zKCkpIHsKICAgICAgICAkcGFydHMgPSAkbGluZS5TcGxpdCgnfCcpCiAgICAgICAgaWYgKCRwYXJ0cy5Db3VudCAtbHQgNCkgeyBjb250aW51ZSB9CiAgICAgICAgJGlkID0gW2ludF0kcGFydHNbMF0KICAgICAgICAkZG9tYWluID0gW3N0cmluZ10kcGFydHNbMV0KICAgICAgICAkdXNlciA9IFtzdHJpbmddJHBhcnRzWzJdCiAgICAgICAgaWYgKCRpZCAtbGUgMCAtb3IgW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkdXNlcikpIHsgY29udGludWUgfQogICAgICAgICRhY2NvdW50ID0gaWYgKFtzdHJpbmddOjpJc051bGxPcldoaXRlU3BhY2UoJGRvbWFpbikpIHsgJHVzZXIgfSBlbHNlIHsgIiRkb21haW5cJHVzZXIiIH0KICAgICAgICAkc2lkID0gJG51bGwKICAgICAgICB0cnkgeyAkc2lkID0gKFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLk5UQWNjb3VudF06Om5ldygkYWNjb3VudCkpLlRyYW5zbGF0ZShbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5TZWN1cml0eUlkZW50aWZpZXJdKS5WYWx1ZSB9IGNhdGNoIHsgfQogICAgICAgICRpdGVtcyArPSBbcHNjdXN0b21vYmplY3RdQHtzZXNzaW9uSWQ9JGlkO2FjY291bnROYW1lPSRhY2NvdW50O3NpZD0kc2lkO3N0YXRlPVtpbnRdJHBhcnRzWzNdfQogICAgfQogICAgcmV0dXJuICRpdGVtcwp9Cgp0cnkgewogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFN0YXRlUGF0aCkpIHsgcmV0dXJuIH0KICAgICRzY3JpcHQ6U3RhdGUgPSBHZXQtQ29udGVudCAtTGl0ZXJhbFBhdGggJFN0YXRlUGF0aCAtUmF3IC1FcnJvckFjdGlvbiBTdG9wIHwgQ29udmVydEZyb20tSnNvbiAtRXJyb3JBY3Rpb24gU3RvcAogICAgaWYgKFtzdHJpbmddJHNjcmlwdDpTdGF0ZS5zdGF0dXMgLW5vdGluIEAoJ0FDVElWRV9ESVNSVVBUSU9OX1BFTkRJTkcnLCdBQ1RJVkVfUEVORElOR19SRUJPT1QnKSkgeyByZXR1cm4gfQogICAgSW5pdGlhbGl6ZS1XdHNOYXRpdmVUeXBlCgogICAgJHN0YWdlU3RhcnQgPSBbRGF0ZVRpbWVdOjpVdGNOb3cKICAgIHRyeSB7CiAgICAgICAgJG9zID0gR2V0LUNpbUluc3RhbmNlIFdpbjMyX09wZXJhdGluZ1N5c3RlbSAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgICRzY3JpcHQ6U3RhdGUuYWN0aW9ucy5ib290VGltZUJlZm9yZSA9IChbZGF0ZXRpbWVdJG9zLkxhc3RCb290VXBUaW1lKS5Ub1VuaXZlcnNhbFRpbWUoKS5Ub1N0cmluZygnbycpCiAgICAgICAgJGRlbGF5ID0gW2ludF0kc2NyaXB0OlN0YXRlLmFjdGlvbnMucmVib290RGVsYXlTZWNvbmRzCiAgICAgICAgaWYgKCRkZWxheSAtbHQgMzApIHsgJGRlbGF5ID0gMzAgfQogICAgICAgICRhcmd1bWVudHMgPSBAKCcvcicsJy9mJywnL3QnLFtzdHJpbmddJGRlbGF5LCcvYycsJyJFbWVyZ2VuY3kgYWNjZXNzIGxvY2tkb3duOiBtYW5kYXRvcnkgcmVzdGFydCInKQogICAgICAgICRwcm9jZXNzID0gU3RhcnQtUHJvY2VzcyAtRmlsZVBhdGggIiRlbnY6U3lzdGVtUm9vdFxTeXN0ZW0zMlxzaHV0ZG93bi5leGUiIC1Bcmd1bWVudExpc3QgJGFyZ3VtZW50cyAtV2FpdCAtUGFzc1RocnUgLVdpbmRvd1N0eWxlIEhpZGRlbgogICAgICAgIGlmICgkcHJvY2Vzcy5FeGl0Q29kZSAtbmUgMCkgeyB0aHJvdyAic2h1dGRvd24uZXhlIHJldHVybmVkIGV4aXQgY29kZSAkKCRwcm9jZXNzLkV4aXRDb2RlKS4iIH0KICAgICAgICAkc2NyaXB0OlN0YXRlLmFjdGlvbnMucmVib290U2NoZWR1bGVkID0gJHRydWUKICAgICAgICAkc2NyaXB0OlN0YXRlLmFjdGlvbnMucmVib290U2NoZWR1bGVkVXRjID0gR2V0LVV0Y1RpbWVzdGFtcAogICAgICAgIEFkZC1Xb3JrZXJTdGFnZSAtTmFtZSAnU0NIRURVTEVfUkVCT09UJyAtU3RhdHVzICdTVUNDRVNTJyAtU3RhcnRlZCAkc3RhZ2VTdGFydCAtQWZmZWN0ZWRDb3VudCAxIC1FeHBlY3RlZCAnc2h1dGRvd24uZXhlIGFjY2VwdGVkIG1hbmRhdG9yeSByZXN0YXJ0JyAtQWN0dWFsICJFeGl0Q29kZT0wO0RlbGF5PSRkZWxheSBzZWNvbmRzIiAtVmVyaWZpY2F0aW9uICdQRU5ESU5HJwogICAgfQogICAgY2F0Y2ggewogICAgICAgICRzY3JpcHQ6U3RhdGUuYWN0aW9ucy5yZWJvb3RTY2hlZHVsZWQgPSAkZmFsc2UKICAgICAgICBBZGQtV29ya2VyU3RhZ2UgLU5hbWUgJ1NDSEVEVUxFX1JFQk9PVCcgLVN0YXR1cyAnRkFJTEVEJyAtU3RhcnRlZCAkc3RhZ2VTdGFydCAtRXJyb3JDb2RlICdSRUJPT1RfU0NIRURVTEVfRkFJTEVEJyAtRXJyb3JNZXNzYWdlICRfLkV4Y2VwdGlvbi5NZXNzYWdlIC1FeHBlY3RlZCAnc2h1dGRvd24uZXhlIGFjY2VwdGVkIG1hbmRhdG9yeSByZXN0YXJ0JyAtQWN0dWFsICdOb3Qgc2NoZWR1bGVkJyAtVmVyaWZpY2F0aW9uICdGQUlMJwogICAgfQoKICAgICRzdGFnZVN0YXJ0ID0gW0RhdGVUaW1lXTo6VXRjTm93CiAgICAkc2Vzc2lvbnMgPSBAKEdldC1XdHNTZXNzaW9ucykKICAgICR0YXJnZXRTaWRzID0gQCgkc2NyaXB0OlN0YXRlLnRhcmdldHMgfCBXaGVyZS1PYmplY3QgeyAkXy5yZXNvbHZlU3RhdHVzIC1lcSAnU1VDQ0VTUycgLWFuZCAtbm90IFtzdHJpbmddOjpJc051bGxPcldoaXRlU3BhY2UoW3N0cmluZ10kXy5zaWQpIH0gfCBGb3JFYWNoLU9iamVjdCB7IFtzdHJpbmddJF8uc2lkIH0gfCBTZWxlY3QtT2JqZWN0IC1VbmlxdWUpCiAgICAkcGVuZGluZ0lkcyA9IEAoKQogICAgdHJ5IHsgJHBlbmRpbmdJZHMgPSBAKCRzY3JpcHQ6U3RhdGUuYWN0aW9ucy5zZXNzaW9uVGVsZW1ldHJ5LnBlbmRpbmdMb2dvZmZTZXNzaW9uSWRzIHwgRm9yRWFjaC1PYmplY3QgeyBbaW50XSRfIH0pIH0gY2F0Y2ggeyB9CiAgICAkdGFyZ2V0U2Vzc2lvbnMgPSBAKCRzZXNzaW9ucyB8IFdoZXJlLU9iamVjdCB7ICgkXy5zaWQgLWFuZCAkXy5zaWQgLWluICR0YXJnZXRTaWRzKSAtb3IgJF8uc2Vzc2lvbklkIC1pbiAkcGVuZGluZ0lkcyB9IHwgU29ydC1PYmplY3Qgc2Vzc2lvbklkIC1VbmlxdWUpCiAgICAkYWNjZXB0ZWQgPSAwCiAgICAkcmVxdWVzdEZhaWx1cmVzID0gQCgpCiAgICBmb3JlYWNoICgkc2Vzc2lvbiBpbiAkdGFyZ2V0U2Vzc2lvbnMpIHsKICAgICAgICB0cnkgeyBbRW1lcmdlbmN5U2VjdXJpdHkuRGlzcnVwdGlvbk5hdGl2ZVYxNDBdOjpMb2dvZmYoW2ludF0kc2Vzc2lvbi5zZXNzaW9uSWQpOyAkYWNjZXB0ZWQrKyB9CiAgICAgICAgY2F0Y2ggeyAkcmVxdWVzdEZhaWx1cmVzICs9ICJTZXNzaW9uICQoJHNlc3Npb24uc2Vzc2lvbklkKSAoJCgkc2Vzc2lvbi5hY2NvdW50TmFtZSkpOiAkKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSIgfQogICAgfQogICAgU3RhcnQtU2xlZXAgLVNlY29uZHMgMwogICAgJHJlbWFpbmluZ05vdyA9IEAoR2V0LVd0c1Nlc3Npb25zIHwgV2hlcmUtT2JqZWN0IHsgKCRfLnNpZCAtYW5kICRfLnNpZCAtaW4gJHRhcmdldFNpZHMpIC1vciAkXy5zZXNzaW9uSWQgLWluICRwZW5kaW5nSWRzIH0pCiAgICAkY29uZmlybWVkQWJzZW50ID0gW2ludF1bTWF0aF06Ok1heCgwLCAoJHRhcmdldFNlc3Npb25zLkNvdW50IC0gJHJlbWFpbmluZ05vdy5Db3VudCkpCiAgICAkdGVsZW1ldHJ5PSRzY3JpcHQ6U3RhdGUuYWN0aW9ucy5zZXNzaW9uVGVsZW1ldHJ5CiAgICAkdGVsZW1ldHJ5LnRhcmdldFNlc3Npb25zRm91bmQgPSBbaW50XSR0YXJnZXRTZXNzaW9ucy5Db3VudAogICAgJHRlbGVtZXRyeS5jb25maXJtZWRDbG9zZWRCZWZvcmVSZWJvb3QgPSAkY29uZmlybWVkQWJzZW50CiAgICAkdGVsZW1ldHJ5LmxvZ29mZlJlcXVlc3RzQWNjZXB0ZWQgPSBbaW50XSRhY2NlcHRlZAogICAgJHRlbGVtZXRyeS5sb2dvZmZSZXF1ZXN0c0ZhaWxlZCA9IFtpbnRdJHJlcXVlc3RGYWlsdXJlcy5Db3VudAogICAgJHRlbGVtZXRyeS5yZW1haW5pbmdTZXNzaW9uSWRzQmVmb3JlUmVib290ID0gQCgkcmVtYWluaW5nTm93IHwgRm9yRWFjaC1PYmplY3QgeyBbaW50XSRfLnNlc3Npb25JZCB9KQogICAgaWYgKCRyZXF1ZXN0RmFpbHVyZXMuQ291bnQgLWd0IDApIHsKICAgICAgICBBZGQtV29ya2VyU3RhZ2UgLU5hbWUgJ0xPR09GRl9TRVNTSU9OUycgLVN0YXR1cyAnV0FSTklORycgLVN0YXJ0ZWQgJHN0YWdlU3RhcnQgLUFmZmVjdGVkQ291bnQgJGFjY2VwdGVkIC1FcnJvckNvZGUgJ09ORV9PUl9NT1JFX0xPR09GRl9SRVFVRVNUU19GQUlMRUQnIC1FcnJvck1lc3NhZ2UgKCRyZXF1ZXN0RmFpbHVyZXMgLWpvaW4gJyB8ICcpIC1FeHBlY3RlZCAnV2luZG93cyBhY2NlcHRzIGV2ZXJ5IHRhcmdldC1zZXNzaW9uIGxvZ29mZiByZXF1ZXN0OyBmaW5hbCBhYnNlbmNlIGlzIHZlcmlmaWVkIGFmdGVyIHJlYm9vdCcgLUFjdHVhbCAiJGFjY2VwdGVkIGFjY2VwdGVkOyAkKCRyZXF1ZXN0RmFpbHVyZXMuQ291bnQpIGZhaWxlZDsgJCgkcmVtYWluaW5nTm93LkNvdW50KSBzdGlsbCB2aXNpYmxlIGJlZm9yZSByZWJvb3QiIC1WZXJpZmljYXRpb24gJ1dBUk5JTkcnCiAgICB9CiAgICBlbHNlaWYgKCRyZW1haW5pbmdOb3cuQ291bnQgLWd0IDApIHsKICAgICAgICBBZGQtV29ya2VyU3RhZ2UgLU5hbWUgJ0xPR09GRl9TRVNTSU9OUycgLVN0YXR1cyAnU1VDQ0VTUycgLVN0YXJ0ZWQgJHN0YWdlU3RhcnQgLUFmZmVjdGVkQ291bnQgJGFjY2VwdGVkIC1FeHBlY3RlZCAnV2luZG93cyBhY2NlcHRzIGV2ZXJ5IHRhcmdldC1zZXNzaW9uIGxvZ29mZiByZXF1ZXN0OyBmaW5hbCBhYnNlbmNlIGlzIHZlcmlmaWVkIGFmdGVyIHJlYm9vdCcgLUFjdHVhbCAiJGFjY2VwdGVkIGFjY2VwdGVkOyAkKCRyZW1haW5pbmdOb3cuQ291bnQpIHN0aWxsIGNsb3NpbmcgYmVmb3JlIHJlYm9vdCIgLVZlcmlmaWNhdGlvbiAnUEVORElORycKICAgIH0KICAgIGVsc2UgewogICAgICAgIEFkZC1Xb3JrZXJTdGFnZSAtTmFtZSAnTE9HT0ZGX1NFU1NJT05TJyAtU3RhdHVzICdTVUNDRVNTJyAtU3RhcnRlZCAkc3RhZ2VTdGFydCAtQWZmZWN0ZWRDb3VudCAkYWNjZXB0ZWQgLUV4cGVjdGVkICdXaW5kb3dzIGFjY2VwdHMgZXZlcnkgdGFyZ2V0LXNlc3Npb24gbG9nb2ZmIHJlcXVlc3QgYW5kIHRhcmdldCBzZXNzaW9ucyBkaXNhcHBlYXInIC1BY3R1YWwgIiQoJHRhcmdldFNlc3Npb25zLkNvdW50KSB0YXJnZXRlZDsgMCByZW1haW4iIC1WZXJpZmljYXRpb24gJ1BBU1MnCiAgICB9CgogICAgJHN0YWdlU3RhcnQgPSBbRGF0ZVRpbWVdOjpVdGNOb3cKICAgICRzY3JpcHQ6U3RhdGUuY29tcGxldGVkVXRjID0gR2V0LVV0Y1RpbWVzdGFtcAogICAgJGhhcmRGYWlsdXJlcyA9IEAoJHNjcmlwdDpTdGF0ZS5zdGFnZXMgfCBXaGVyZS1PYmplY3QgeyAkXy5zdGF0dXMgLWVxICdGQUlMRUQnIH0pLkNvdW50CiAgICAkc2NyaXB0OlN0YXRlLnN0YXR1cyA9IGlmIChbYm9vbF0kc2NyaXB0OlN0YXRlLmFjdGlvbnMucmVib290U2NoZWR1bGVkIC1hbmQgJGhhcmRGYWlsdXJlcyAtZXEgMCkgeyAnQUNUSVZFX1BFTkRJTkdfUkVCT09UJyB9IGVsc2UgeyAnTE9DS0RPV05fUEFSVElBTCcgfQogICAgQWRkLVdvcmtlclN0YWdlIC1OYW1lICdGSU5BTElaRV9TVEFURScgLVN0YXR1cyAkKGlmKCRzY3JpcHQ6U3RhdGUuc3RhdHVzIC1lcSAnQUNUSVZFX1BFTkRJTkdfUkVCT09UJyl7J1NVQ0NFU1MnfWVsc2V7J1dBUk5JTkcnfSkgLVN0YXJ0ZWQgJHN0YWdlU3RhcnQgLUVycm9yQ29kZSAkKGlmKCRzY3JpcHQ6U3RhdGUuc3RhdHVzIC1lcSAnQUNUSVZFX1BFTkRJTkdfUkVCT09UJyl7JG51bGx9ZWxzZXsnTE9DS0RPV05fUEFSVElBTCd9KSAtRXJyb3JNZXNzYWdlICQoaWYoJHNjcmlwdDpTdGF0ZS5zdGF0dXMgLWVxICdBQ1RJVkVfUEVORElOR19SRUJPT1QnKXskbnVsbH1lbHNleydEaXNydXB0aW9uIHdvcmtlciBjb21wbGV0ZWQgd2l0aCBvbmUgb3IgbW9yZSBmYWlsdXJlcy4nfSkgLUV4cGVjdGVkICdEdXJhYmxlIHByZS1yZWJvb3Qgc3RhdGUnIC1BY3R1YWwgKFtzdHJpbmddJHNjcmlwdDpTdGF0ZS5zdGF0dXMpIC1WZXJpZmljYXRpb24gJChpZigkc2NyaXB0OlN0YXRlLnN0YXR1cyAtZXEgJ0FDVElWRV9QRU5ESU5HX1JFQk9PVCcpeydQQVNTJ31lbHNleydXQVJOSU5HJ30pCn0KY2F0Y2ggewogICAgdHJ5IHsKICAgICAgICBpZiAoJG51bGwgLWVxICRzY3JpcHQ6U3RhdGUgLWFuZCAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkU3RhdGVQYXRoKSkgeyAkc2NyaXB0OlN0YXRlID0gR2V0LUNvbnRlbnQgLUxpdGVyYWxQYXRoICRTdGF0ZVBhdGggLVJhdyB8IENvbnZlcnRGcm9tLUpzb24gfQogICAgICAgIGlmICgkbnVsbCAtbmUgJHNjcmlwdDpTdGF0ZSkgewogICAgICAgICAgICAkc2NyaXB0OlN0YXRlLnN0YXR1cyA9ICdMT0NLRE9XTl9QQVJUSUFMJwogICAgICAgICAgICBBZGQtV29ya2VyU3RhZ2UgLU5hbWUgJ0RJU1JVUFRJT05fV09SS0VSJyAtU3RhdHVzICdGQUlMRUQnIC1TdGFydGVkIChbRGF0ZVRpbWVdOjpVdGNOb3cpIC1FcnJvckNvZGUgJ0RJU1JVUFRJT05fV09SS0VSX0VYQ0VQVElPTicgLUVycm9yTWVzc2FnZSAkXy5FeGNlcHRpb24uTWVzc2FnZSAtRXhwZWN0ZWQgJ0xvZ29mZiBhbmQgcmVib290IG9yY2hlc3RyYXRpb24nIC1BY3R1YWwgJ1VuaGFuZGxlZCB3b3JrZXIgZmFpbHVyZScgLVZlcmlmaWNhdGlvbiAnRkFJTCcKICAgICAgICB9CiAgICB9IGNhdGNoIHsgfQp9CmZpbmFsbHkgewogICAgdHJ5IHsgJiAiJGVudjpTeXN0ZW1Sb290XFN5c3RlbTMyXHNjaHRhc2tzLmV4ZSIgL0RlbGV0ZSAvVE4gJFRhc2tOYW1lIC9GIHwgT3V0LU51bGwgfSBjYXRjaCB7IH0KICAgIHRyeSB7IFJlbW92ZS1JdGVtIC1MaXRlcmFsUGF0aCAkUFNDb21tYW5kUGF0aCAtRm9yY2UgLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUgfSBjYXRjaCB7IH0KfQo='
$script:StateWriteEnabled = $false
$script:State = [ordered]@{
schemaVersion=3;scriptVersion=$ScriptVersion;status='INITIALIZING';operation='LOCKDOWN';scope=$null
computer=$env:COMPUTERNAME;startedUtc=([DateTime]::UtcNow.ToString('o'));completedUtc=$null
requestedTargets=@();targets=@()
laps=[ordered]@{requested=[bool]$SaveLaps;detected=$false;verified=$false;accountName=$null;accountSid=$null;policySource=$null;verificationLevel=$null;accountResolutionSource=$null;lastPolicySuccessUtc=$null;lastBackupSuccessUtc=$null;lastLocalUpdateUtc=$null;blockingDenyRights=@();validationError=$null}
originalState=[ordered]@{cachedLogonsCount=$null;netlogon=$null;kdcProxy=$null;userRights=@()}
evidence=[ordered]@{sessions=@();processes=@();scheduledTasks=@();services=@();networkConnections=@()}
actions=[ordered]@{
changesApplied=$false;cachedDomainLogon='NOT_STARTED';denyRights='NOT_STARTED';protectedIdentityCount=0
scopeGuard=[ordered]@{mode='NOT_APPLICABLE';status='NOT_STARTED';principals=@();verifiedCount=0}
domainAuthIsolation='NOT_APPLICABLE';domainContext=$null;firewallEngine=$null
authFirewall=[ordered]@{mode='NOT_APPLICABLE';domainName=$null;dcNames=@();remoteAddresses=@();ruleDefinitions=@();warningCode=$null}
sessionTelemetry=[ordered]@{targetSessionsFound=0;logoffRequestsAccepted=0;logoffRequestsFailed=0;confirmedClosedBeforeReboot=0;remainingSessionIdsBeforeReboot=@();targetIdentitiesWithoutSessionAfterReboot=0;remainingTargetSessionsAfterReboot=@();pendingLogoffSessionIds=@()}
rebootScheduled=$false;rebootDelaySeconds=$RebootDelaySeconds
rebootScheduledUtc=$null;bootTimeBefore=$null;executionContext=[ordered]@{identityName=$null;identitySid=$null;processSessionId=$null;isSystem=$false;isAdministrator=$false};disruptionWorker=[ordered]@{taskName=$DisruptionTaskName;scriptPath=$DisruptionWorkerPath;installed=$false;started=$false};stateFile=$StatePath
}
stages=@();warnings=@();errors=@()
}
function Get-UtcTimestamp {
return [DateTime]::UtcNow.ToString('o')
}
function Add-WarningRecord {
param([string]$Code, [string]$Message)
$script:State.warnings += [pscustomobject][ordered]@{
code = $Code
message = $Message
timestampUtc = Get-UtcTimestamp
}
}
function Add-ErrorRecord {
param([string]$Code, [string]$Message, [string]$Stage)
$script:State.errors += [pscustomobject][ordered]@{
code = $Code
message = $Message
stage = $Stage
timestampUtc = Get-UtcTimestamp
}
}
function Set-SecureDirectoryAcl {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
$null = New-Item -Path $Path -ItemType Directory -Force
}
$systemSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
$administratorsSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
$inheritance = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
[System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$propagation = [System.Security.AccessControl.PropagationFlags]::None
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$fullControl = [System.Security.AccessControl.FileSystemRights]::FullControl
$security = [System.Security.AccessControl.DirectorySecurity]::new()
$security.SetAccessRuleProtection($true, $false)
$security.SetOwner($systemSid)
$security.AddAccessRule(([System.Security.AccessControl.FileSystemAccessRule]::new($systemSid, $fullControl, $inheritance, $propagation, $allow)))
$security.AddAccessRule(([System.Security.AccessControl.FileSystemAccessRule]::new($administratorsSid, $fullControl, $inheritance, $propagation, $allow)))
$directoryInfo = [System.IO.DirectoryInfo]::new($Path)
$directoryInfo.SetAccessControl($security)
}
function Set-SecureFileAcl {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return }
$systemSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
$administratorsSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$fullControl = [System.Security.AccessControl.FileSystemRights]::FullControl
$security = [System.Security.AccessControl.FileSecurity]::new()
$security.SetAccessRuleProtection($true, $false)
$security.SetOwner($systemSid)
$security.AddAccessRule(([System.Security.AccessControl.FileSystemAccessRule]::new($systemSid, $fullControl, $allow)))
$security.AddAccessRule(([System.Security.AccessControl.FileSystemAccessRule]::new($administratorsSid, $fullControl, $allow)))
$fileInfo = [System.IO.FileInfo]::new($Path)
$fileInfo.SetAccessControl($security)
}
function Write-StateCheckpoint {
if (-not $script:StateWriteEnabled) { return }
Set-SecureDirectoryAcl -Path $BaseDirectory
$json = $script:State | ConvertTo-Json -Depth 24
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($TemporaryStatePath, $json, $utf8NoBom)
Set-SecureFileAcl -Path $TemporaryStatePath
Move-Item -LiteralPath $TemporaryStatePath -Destination $StatePath -Force
Set-SecureFileAcl -Path $StatePath
}
function Add-StageResult {
param(
[Parameter(Mandatory = $true)][string]$Name,
[Parameter(Mandatory = $true)][ValidateSet('SUCCESS','WARNING','FAILED','SKIPPED','NOT_APPLICABLE','PENDING')][string]$Status,
[datetime]$Started,
[int]$AffectedCount = 0,
[string]$ErrorCode = $null,
[string]$ErrorMessage = $null,
[string]$Message = $null,
[string]$Expected = $null,
[string]$Actual = $null,
[ValidateSet('PASS','WARNING','FAIL','PENDING','NOT_CHECKED','NOT_APPLICABLE')][string]$Verification = 'NOT_CHECKED'
)
if ($null -eq $Started) { $Started = [DateTime]::UtcNow }
if ($Verification -eq 'NOT_CHECKED') {
$Verification = switch ($Status) { 'SUCCESS' {'PASS'} 'WARNING' {'WARNING'} 'FAILED' {'FAIL'} 'NOT_APPLICABLE' {'NOT_APPLICABLE'} default {'NOT_CHECKED'} }
}
$completed = [DateTime]::UtcNow
$script:State.stages += [pscustomobject][ordered]@{
name=$Name;status=$Status;verification=$Verification
startedUtc=$Started.ToString('o');completedUtc=$completed.ToString('o')
durationMs=[int][Math]::Max(0,($completed-$Started).TotalMilliseconds);affectedCount=$AffectedCount
expected=$Expected;actual=$Actual;errorCode=$ErrorCode;errorMessage=$ErrorMessage;message=$Message
}
if ($Status -eq 'WARNING' -and $ErrorCode) { Add-WarningRecord -Code $ErrorCode -Message $ErrorMessage }
elseif ($Status -eq 'FAILED' -and $ErrorCode) { Add-ErrorRecord -Code $ErrorCode -Message $ErrorMessage -Stage $Name }
Write-StateCheckpoint
}
function Get-StageStatus {
param([string]$Name)
$item=@($script:State.stages | Where-Object {$_.name -eq $Name} | Select-Object -Last 1)
if($item.Count -eq 0){return 'NOT_REACHED'}
return [string]$item[0].status
}
function Convert-StageStatusForRemarks {
param([string]$Status)
switch($Status){'SUCCESS'{'PASS'}'WARNING'{'WARN'}'FAILED'{'FAIL'}'PENDING'{'PENDING'}'NOT_APPLICABLE'{'N/A'}'SKIPPED'{'SKIP'}default{'NOT_REACHED'}}
}
function Get-CombinedCheckForRemarks {
param([string[]]$Stages)
$statuses=@($Stages | ForEach-Object { Get-StageStatus $_ })
if('FAILED' -in $statuses){return 'FAIL'}
if('WARNING' -in $statuses){return 'WARN'}
if('PENDING' -in $statuses){return 'PENDING'}
if(@($statuses | Where-Object {$_ -eq 'SUCCESS'}).Count -gt 0 -and @($statuses | Where-Object {$_ -eq 'NOT_REACHED'}).Count -eq 0){return 'PASS'}
if(@($statuses | Where-Object {$_ -eq 'NOT_APPLICABLE'}).Count -eq $statuses.Count){return 'N/A'}
if('SKIPPED' -in $statuses){return 'SKIP'}
return 'NOT_REACHED'
}
function Get-OverallStatus {
if($script:State.status -eq 'FAILED_PRECHECK'){return 'FAILED_PRECHECK'}
$failed=@($script:State.stages | Where-Object {$_.status -eq 'FAILED'}).Count
$warnings=@($script:State.stages | Where-Object {$_.status -eq 'WARNING'}).Count
if($failed -gt 0 -and -not $script:State.actions.changesApplied){return 'FAILED'}
if($failed -gt 0){return 'PARTIAL_FAILURE'}
if($warnings -gt 0 -or @($script:State.warnings).Count -gt 0){return 'SUCCESS_WITH_WARNINGS'}
if($script:State.status -eq 'ACTIVE_DISRUPTION_PENDING'){return 'SUCCESS_DISRUPTION_STARTED'}
if($script:State.status -eq 'ACTIVE_PENDING_REBOOT'){return 'SUCCESS_REBOOT_QUEUED'}
return 'SUCCESS'
}
function Write-Remarks {
$resolved=@($script:State.targets | Where-Object {$_.resolveStatus -eq 'SUCCESS'}).Count
$requested=@($script:State.requestedTargets).Count
$allScope=($script:State.scope -like 'ALL_*')
$unresolved=if($allScope){0}else{[Math]::Max(0,($requested-$resolved))}
if($allScope){
$discoveryStatus=Get-StageStatus 'DISCOVER_ALL_USERS'
$targetSummary=if($discoveryStatus -eq 'NOT_REACHED'){'NOT_REACHED'}else{"$resolved discovered"}
}else{
$targetSummary="$resolved/$requested"
}
$failedStages=@($script:State.stages | Where-Object {$_.status -eq 'FAILED'} | ForEach-Object {$_.name})
$warningStages=@($script:State.stages | Where-Object {$_.status -eq 'WARNING' -and $_.name -ne 'RESOLVE_TARGETS'} | ForEach-Object {$_.name})
$result=if($script:State.status -eq 'FAILED_PRECHECK'){'FAILED_PRECHECK'}
elseif($failedStages.Count -gt 0 -and -not [bool]$script:State.actions.changesApplied){'FAILED'}
elseif($failedStages.Count -gt 0){'ENFORCEMENT_INCOMPLETE'}
elseif($warningStages.Count -gt 0 -or @($script:State.warnings).Count -gt 0 -or $unresolved -gt 0){'ENFORCEMENT_APPLIED_WITH_WARNINGS'}
else{'ENFORCEMENT_APPLIED'}
$auth=(Get-CombinedCheckForRemarks @('DISABLE_NETLOGON','DISABLE_KDC_PROXY','APPLY_AUTH_FIREWALL'))
if($script:State.scope -like 'ALL_*' -and $auth -eq 'PASS' -and $script:State.actions.authFirewall -and $script:State.actions.authFirewall.warningCode){$auth='PASS_WITH_FALLBACK'}
$guardStage=Get-StageStatus 'PREPARE_SCOPE_GUARD'
$guard=if(-not $allScope){'N/A'}
elseif($guardStage -eq 'NOT_REACHED'){'NOT_REACHED'}
elseif([string]$script:State.actions.scopeGuard.status -eq 'VERIFIED'){'PASS'}
elseif([string]$script:State.actions.scopeGuard.status -eq 'PREPARED'){'PENDING'}
elseif([string]$script:State.actions.scopeGuard.status -eq 'UNAVAILABLE'){'WARN'}
else{'FAIL'}
$summary="EmergencyLockdown v$ScriptVersion | $result | Scope=$($script:State.scope) | Targets=$targetSummary"
if([bool]$SaveLaps){$summary += " | LAPS=$(Convert-StageStatusForRemarks (Get-StageStatus 'DETECT_LAPS'))"}
$summary += " | Guard=$guard | Rights=$(Convert-StageStatusForRemarks (Get-StageStatus 'APPLY_DENY_RIGHTS')) | CacheConfig=$(Convert-StageStatusForRemarks (Get-StageStatus 'DISABLE_CACHED_DOMAIN_LOGON')) | AuthDefense=$auth"
if($unresolved -gt 0){$summary += " | Unresolved=$unresolved"}
if($failedStages.Count -gt 0){$summary += " | Failed=$($failedStages -join ',')"}
$contextWarning=@($warningStages | Where-Object {$_ -eq 'DISCOVER_DOMAIN_CONTEXT'}).Count -gt 0
if($contextWarning){$summary += " | Context=PARTIAL"}
$otherWarnings=@($warningStages | Where-Object {$_ -ne 'DISCOVER_DOMAIN_CONTEXT'})
if($otherWarnings.Count -gt 0){$summary += " | Warn=$($otherWarnings -join ',')"}
$summary += " | State=$StatePath"
[Console]::Out.WriteLine($summary)
}
function Test-SecurePathAcl {
param([string]$Path)
$acl=Get-Acl -LiteralPath $Path -ErrorAction Stop
$ownerSid=try{([System.Security.Principal.NTAccount]$acl.Owner).Translate([System.Security.Principal.SecurityIdentifier]).Value}catch{[string]$acl.Owner}
$allowed=@('S-1-5-18','S-1-5-32-544');$unexpected=@()
foreach($rule in @($acl.Access)){
$sid=try{$rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value}catch{[string]$rule.IdentityReference}
if($rule.AccessControlType -ne 'Allow' -or $sid -notin $allowed){$unexpected += $sid}
}
[pscustomobject]@{Pass=($acl.AreAccessRulesProtected -and $ownerSid -eq 'S-1-5-18' -and $unexpected.Count -eq 0);Owner=$ownerSid;Protected=[bool]$acl.AreAccessRulesProtected;Unexpected=($unexpected -join ';')}
}
function Test-IsSystemOrAdministrator {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
if ($identity.User.Value -eq 'S-1-5-18') { return $true }
$principal = [System.Security.Principal.WindowsPrincipal]::new($identity)
return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-LocalMachineSidPrefix {
try {
$local = @(Get-CimInstance Win32_UserAccount -Filter "LocalAccount=True" -ErrorAction Stop |
Where-Object { $_.SID -like 'S-1-5-21-*' } |
Select-Object -First 1)
if ($local.Count -eq 0) { return $null }
return ($local[0].SID -replace '-\d+$','')
}
catch { return $null }
}
function Get-ProfileRegistryRecords {
$records = @()
$root = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
if (-not (Test-Path $root)) { return $records }
foreach ($key in @(Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
try {
$property = Get-ItemProperty -Path $key.PSPath -Name ProfileImagePath -ErrorAction Stop
$expanded = [Environment]::ExpandEnvironmentVariables([string]$property.ProfileImagePath)
$records += [pscustomobject]@{
sid = $key.PSChildName
profilePath = $expanded
leafName = Split-Path -Path $expanded -Leaf
}
}
catch { }
}
return $records
}
function Normalize-AccountName {
param([string]$Name)
$value = $Name.Trim()
if ($value.StartsWith('.\')) {
return "$env:COMPUTERNAME\$($value.Substring(2))"
}
return $value
}
function Resolve-AccountIdentity {
param([Parameter(Mandatory = $true)][string]$RequestedName)
$normalized = Normalize-AccountName -Name $RequestedName
$result = [ordered]@{
requested=$RequestedName;normalized=$normalized;sid=$null;accountName=$null;isLocal=$false
profileFound=$false;profilePath=$null;resolutionMethod=$null;resolveStatus='NOT_RESOLVED'
errorCode=$null;errorMessage=$null;sources=@('EXPLICIT_INPUT');persistenceEligible=$true
}
$resolvedFromActiveSession = $false
if ($normalized -notmatch '^S-\d-\d+(-\d+)+$' -and $normalized.Contains('\')) {
$parts=$normalized.Split('\',2)
$domainPart=[string]$parts[0]
$username=[string]$parts[1]
if($domainPart -notin @('.', $env:COMPUTERNAME)) {
try {
$sessionMatches=@([EmergencySecurity.Native]::GetSessions() | Where-Object {
-not [string]::IsNullOrWhiteSpace([string]$_.UserName) -and
("$($_.DomainName)\$($_.UserName)" -ieq $normalized)
})
if($sessionMatches.Count -gt 0) {
$localSidPrefix=Get-LocalMachineSidPrefix
$profileMatches=@(Get-ProfileRegistryRecords | Where-Object {
$_.leafName -ieq $username -and (-not $localSidPrefix -or $_.sid -notlike "$localSidPrefix-*")
})
if($profileMatches.Count -eq 1) {
$result.sid=$profileMatches[0].sid;$result.accountName=$normalized
$result.profileFound=$true;$result.profilePath=$profileMatches[0].profilePath
$result.resolutionMethod='ACTIVE_SESSION_PROFILE_MATCH';$result.resolveStatus='SUCCESS'
$resolvedFromActiveSession=$true
}
}
} catch { }
}
}
if(-not $resolvedFromActiveSession) {
try {
if ($normalized -match '^S-\d-\d+(-\d+)+$') {
$sidObject=[System.Security.Principal.SecurityIdentifier]::new($normalized)
$result.sid=$sidObject.Value
try{$result.accountName=$sidObject.Translate([System.Security.Principal.NTAccount]).Value}catch{$result.accountName=$normalized}
$result.resolutionMethod='SID_INPUT';$result.resolveStatus='SUCCESS'
} else {
$account=[System.Security.Principal.NTAccount]::new($normalized)
$sidObject=$account.Translate([System.Security.Principal.SecurityIdentifier])
$result.sid=$sidObject.Value;$result.accountName=$normalized
$result.resolutionMethod='ACCOUNT_TRANSLATION';$result.resolveStatus='SUCCESS'
}
}
catch {
$username=if($normalized.Contains('\')){$normalized.Split('\')[-1]}else{$normalized}
$domainPart=if($normalized.Contains('\')){$normalized.Split('\')[0]}else{$null}
$localSidPrefix=Get-LocalMachineSidPrefix
$profiles=@(Get-ProfileRegistryRecords)
$matches=@($profiles | Where-Object {$_.leafName -ieq $username -or $_.leafName -like "$username.*" -or $_.leafName -like "$username-*"})
if($domainPart -and $domainPart -notin @('.', $env:COMPUTERNAME) -and $localSidPrefix){$matches=@($matches | Where-Object {$_.sid -notlike "$localSidPrefix-*"})}
if($matches.Count -eq 1){
$result.sid=$matches[0].sid;$result.accountName=$normalized;$result.profileFound=$true
$result.profilePath=$matches[0].profilePath;$result.resolutionMethod='UNIQUE_PROFILE_MATCH';$result.resolveStatus='SUCCESS'
} else {
$result.errorCode=if($matches.Count -gt 1){'AMBIGUOUS_PROFILE_MATCH'}else{'SID_NOT_RESOLVED'}
$result.errorMessage=$_.Exception.Message
}
}
}
if($result.resolveStatus -eq 'SUCCESS'){
$localPrefix=Get-LocalMachineSidPrefix
if($localPrefix -and $result.sid -like "$localPrefix-*"){$result.isLocal=$true}
if(-not $result.profileFound){
$profile=@(Get-ProfileRegistryRecords | Where-Object {$_.sid -eq $result.sid} | Select-Object -First 1)
if($profile.Count -gt 0){$result.profileFound=$true;$result.profilePath=$profile[0].profilePath}
}
}
return [pscustomobject]$result
}
function Test-IsExcludedSystemSid {
param([string]$Sid, [string]$AccountName)
if ($Sid -in @('S-1-5-18','S-1-5-19','S-1-5-20')) { return $true }
if ($Sid -like 'S-1-5-80-*') { return $true }
if ($Sid -like 'S-1-5-82-*') { return $true }
if ($AccountName -like 'NT SERVICE\*') { return $true }
if ($AccountName -like 'NT AUTHORITY\*') { return $true }
if ($AccountName -match '\$$') { return $true }
return $false
}
function Get-DiscoveredHumanAccounts {
$accounts = @()
try {
foreach ($local in @(Get-CimInstance Win32_UserAccount -Filter "LocalAccount=True" -ErrorAction Stop)) {
$name = "$env:COMPUTERNAME\$($local.Name)"
if (-not (Test-IsExcludedSystemSid -Sid $local.SID -AccountName $name)) {
$accounts += [pscustomobject]@{
requested = $name
normalized = $name
sid = $local.SID
accountName = $name
isLocal = $true
profileFound = $false
profilePath = $null
resolutionMethod = 'LOCAL_SAM'
resolveStatus = 'SUCCESS'
errorCode = $null
errorMessage = $null
sources = @('LOCAL_SAM')
persistenceEligible = $false
}
}
}
}
catch { }
$localSidPrefix=Get-LocalMachineSidPrefix
foreach ($profile in @(Get-ProfileRegistryRecords)) {
if ($profile.sid -notmatch '^S-1-5-21-') { continue }
$existing = @($accounts | Where-Object { $_.sid -eq $profile.sid })
if ($existing.Count -gt 0) {
$existing[0].profileFound = $true
$existing[0].profilePath = $profile.profilePath
$existing[0].persistenceEligible = $true
$existing[0].sources = @($existing[0].sources + 'PROFILE_LIST' | Select-Object -Unique)
continue
}
# Do not translate domain SIDs here. Translation can block for a long time while the
# device is away from a DC. The SID is the enforcement key; the profile leaf is only
# a readable label and is improved later from an active session when available.
$name = if($localSidPrefix -and $profile.sid -like "$localSidPrefix-*") {
"$env:COMPUTERNAME\$($profile.leafName)"
} else {
[string]$profile.leafName
}
if (-not (Test-IsExcludedSystemSid -Sid $profile.sid -AccountName $name)) {
$accounts += [pscustomobject]@{
requested = $name
normalized = $name
sid = $profile.sid
accountName = $name
isLocal = [bool]($localSidPrefix -and $profile.sid -like "$localSidPrefix-*")
profileFound = $true
profilePath = $profile.profilePath
resolutionMethod = 'PROFILE_LIST'
resolveStatus = 'SUCCESS'
errorCode = $null
errorMessage = $null
sources = @('PROFILE_LIST')
persistenceEligible = $true
}
}
}
return @($accounts | Group-Object sid | ForEach-Object { $_.Group | Select-Object -First 1 })
}
function Get-ObjectPropertyValue {
param([object]$Object, [string]$Name, $DefaultValue = $null)
if ($null -eq $Object) { return $DefaultValue }
$property = $Object.PSObject.Properties[$Name]
if ($null -eq $property) { return $DefaultValue }
return $property.Value
}
function ConvertTo-NullableInt32Flexible {
param([object]$Value)
if ($null -eq $Value) { return $null }
$text = ([string]$Value).Trim()
if ([string]::IsNullOrWhiteSpace($text)) { return $null }
try {
if ($text -match '^0[xX]([0-9A-Fa-f]+)$') {
return [Convert]::ToInt32($matches[1], 16)
}
$parsed = 0
if ([int]::TryParse($text, [ref]$parsed)) {
return $parsed
}
}
catch { }
return $null
}
function Get-ActiveLapsPolicy {
$roots = @(
[pscustomobject]@{ Path='HKLM:\Software\Microsoft\Policies\LAPS'; Source='CSP' },
[pscustomobject]@{ Path='HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\LAPS'; Source='GPO' },
[pscustomobject]@{ Path='HKLM:\Software\Microsoft\Windows\CurrentVersion\LAPS\Config'; Source='LOCAL_CONFIG' }
)
foreach ($root in $roots) {
if (-not (Test-Path $root.Path)) { continue }
$item = Get-ItemProperty -Path $root.Path -ErrorAction SilentlyContinue
$definedProperties = @($item.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' })
if ($definedProperties.Count -gt 0) {
return [pscustomobject]@{ Path=$root.Path; Source=$root.Source; Values=$item }
}
}
$legacyPath = 'HKLM:\Software\Policies\Microsoft Services\AdmPwd'
if (Test-Path $legacyPath) {
$item = Get-ItemProperty -Path $legacyPath -ErrorAction SilentlyContinue
if ($null -ne $item) {
return [pscustomobject]@{ Path=$legacyPath; Source='LEGACY_LAPS'; Values=$item }
}
}
return $null
}
function Get-LocalAdministratorsMemberSids {
$sids = @()
$group = @()
try {
$group = @(Get-CimInstance Win32_Group -Filter "LocalAccount=True AND SID='S-1-5-32-544'" -ErrorAction Stop | Select-Object -First 1)
if ($group.Count -eq 0) { return $sids }
try { $sids = @([EmergencySecurity.Native]::GetLocalGroupMemberSids([string]$group[0].Name)) } catch { }
if ($sids.Count -eq 0) {
try {
foreach ($member in @(Get-CimAssociatedInstance -InputObject $group[0] -Association Win32_GroupUser -ErrorAction Stop)) {
if ($member.SID) { $sids += [string]$member.SID }
}
} catch { }
}
}
catch { }
return @($sids | Where-Object { $_ } | Select-Object -Unique)
}
function Get-VerifiedLapsAccount {
$policy=Get-ActiveLapsPolicy
if($null -eq $policy){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_POLICY_NOT_FOUND';ErrorMessage='No Windows LAPS or legacy-emulation policy was found.'}}
$backupDirectory=$null
if($policy.Source -eq 'LEGACY_LAPS'){
$enabled=Get-ObjectPropertyValue -Object $policy.Values -Name 'AdmPwdEnabled'
if($null -ne $enabled -and [int]$enabled -eq 0){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_POLICY_DISABLED';ErrorMessage='Legacy LAPS policy is disabled.'}}
$backupDirectory=2
}else{
$backupDirectory=Get-ObjectPropertyValue -Object $policy.Values -Name 'BackupDirectory'
if($null -eq $backupDirectory -or [int]$backupDirectory -eq 0){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_BACKUP_DISABLED';ErrorMessage='Windows LAPS BackupDirectory is disabled or undefined.'}}
}
$passwordAge=Get-ObjectPropertyValue -Object $policy.Values -Name 'PasswordAgeDays' -DefaultValue 30
try{$maxAgeDays=[Math]::Max(45,([int]$passwordAge*2)+7)}catch{$maxAgeDays=90}
$cutoff=(Get-Date).AddDays(-$maxAgeDays)
$policyIds=if($policy.Source -eq 'LEGACY_LAPS'){@(10023)}elseif([int]$backupDirectory -eq 1){@(10022)}else{@(10021)}
$backupIds=if([int]$backupDirectory -eq 1){@(10029)}else{@(10018)}
$policyEvent=$null;$backupEvent=$null;$localEvent=$null
try{$policyEvent=Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-LAPS/Operational';Id=$policyIds;StartTime=$cutoff} -MaxEvents 20 -ErrorAction Stop | Sort-Object TimeCreated -Descending | Select-Object -First 1}catch{}
try{$backupEvent=Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-LAPS/Operational';Id=$backupIds;StartTime=$cutoff} -MaxEvents 20 -ErrorAction Stop | Sort-Object TimeCreated -Descending | Select-Object -First 1}catch{}
try{$localEvent=Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-LAPS/Operational';Id=10020;StartTime=$cutoff} -MaxEvents 20 -ErrorAction Stop | Sort-Object TimeCreated -Descending | Select-Object -First 1}catch{}
if($null -eq $policyEvent){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_EFFECTIVE_POLICY_EVENT_NOT_FOUND';ErrorMessage="No recent effective LAPS policy event was found within $maxAgeDays days."}}
if($null -eq $backupEvent){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_BACKUP_SUCCESS_EVENT_NOT_FOUND';ErrorMessage="No recent successful LAPS directory-backup event was found within $maxAgeDays days."}}
if($null -eq $localEvent){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_LOCAL_UPDATE_EVENT_NOT_FOUND';ErrorMessage="No recent successful LAPS local-account update event was found within $maxAgeDays days."}}
$accountName=$null;$accountRid=$null
$accountResolutionSource=$null
$policyAccountName=$null
foreach($propertyName in @('AdministratorAccountName','AdminAccountName')){
$candidate=Get-ObjectPropertyValue -Object $policy.Values -Name $propertyName
if($null -ne $candidate -and -not [string]::IsNullOrWhiteSpace([string]$candidate)){
$policyAccountName=([string]$candidate).Trim()
break
}
}
try{
[xml]$eventXml=$localEvent.ToXml()
$eventData=@{}
foreach($item in @($eventXml.Event.EventData.Data)){
$value=([string]$item.'#text').Trim()
if([string]::IsNullOrWhiteSpace($value)){continue}
$rawName=[string]$item.Name
if(-not [string]::IsNullOrWhiteSpace($rawName)){
$normalizedKey=($rawName -replace '[^A-Za-z0-9]','').ToLowerInvariant()
if(-not $eventData.ContainsKey($normalizedKey)){$eventData[$normalizedKey]=$value}
}
}
foreach($key in @('accountname','managedaccountname','localadministratoraccountname')){
if($eventData.ContainsKey($key)-and -not [string]::IsNullOrWhiteSpace([string]$eventData[$key])){
$accountName=([string]$eventData[$key]).Trim()
$accountResolutionSource='EVENT_XML_ACCOUNT_NAME'
break
}
}
foreach($key in @('accountrid','rid')){
if($eventData.ContainsKey($key)){
$parsedRid=ConvertTo-NullableInt32Flexible -Value $eventData[$key]
if($null -ne $parsedRid){$accountRid=$parsedRid;$accountResolutionSource='EVENT_XML_ACCOUNT_RID';break}
}
}
# Rendered event messages may indent the Account name/RID lines.
if([string]::IsNullOrWhiteSpace($accountName) -and $localEvent.Message -match '(?im)^\s*Account name:\s*(.+?)\s*$'){
$accountName=$matches[1].Trim()
$accountResolutionSource='EVENT_RENDERED_ACCOUNT_NAME'
}
if($null -eq $accountRid -and $localEvent.Message -match '(?im)^\s*Account RID:\s*(0[xX][0-9A-Fa-f]+|\d+)\s*$'){
$accountRid=ConvertTo-NullableInt32Flexible -Value $matches[1]
if($null -ne $accountRid){$accountResolutionSource='EVENT_RENDERED_ACCOUNT_RID'}
}
}catch{
return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_MANAGED_ACCOUNT_EVENT_UNREADABLE';ErrorMessage=$_.Exception.Message}
}
# A rendered 10021 policy event provides a second local source for the configured account name.
$policyEventAccountName=$null
if($policyEvent.Message -match '(?im)^\s*Local administrator account name:\s*(.*?)\s*$'){
$candidate=([string]$matches[1]).Trim()
if(-not [string]::IsNullOrWhiteSpace($candidate)){$policyEventAccountName=$candidate}
}
# Locale-independent fallback: combine the current effective policy with paired successful
# local-account and directory-backup events from the same processing cycle.
if([string]::IsNullOrWhiteSpace($accountName) -and
-not [string]::IsNullOrWhiteSpace($policyAccountName)){
$eventPairDelta=[Math]::Abs(($localEvent.TimeCreated-$backupEvent.TimeCreated).TotalMinutes)
if($eventPairDelta -le 15){
if([string]::IsNullOrWhiteSpace($policyEventAccountName) -or $policyAccountName -ieq $policyEventAccountName){
$accountName=$policyAccountName
$accountResolutionSource='CURRENT_POLICY_PLUS_PAIRED_SUCCESS_EVENTS'
}
}
}
# An empty managed-account name means the built-in Administrator account (RID 500)
# when automatic account management isn't enabled.
if($null -eq $accountRid -and [string]::IsNullOrWhiteSpace($accountName)){
$automaticManagement=Get-ObjectPropertyValue -Object $policy.Values -Name 'AutomaticAccountManagementEnabled' -DefaultValue 0
try{$automaticManagementEnabled=([int]$automaticManagement -ne 0)}catch{$automaticManagementEnabled=$false}
if(-not $automaticManagementEnabled){$accountRid=500;$accountResolutionSource='BUILTIN_ADMINISTRATOR_POLICY_DEFAULT'}
}
if($null -eq $accountRid -and [string]::IsNullOrWhiteSpace($accountName)){
return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_MANAGED_ACCOUNT_UNRESOLVED';ErrorMessage='The successful LAPS local-update event and effective policy did not identify the managed account.'}
}
$localAccounts=@(Get-CimInstance Win32_UserAccount -Filter 'LocalAccount=True' -ErrorAction SilentlyContinue)
$account=@()
if($accountRid){$account=@($localAccounts | Where-Object {$_.SID -match "-$accountRid$"} | Select-Object -First 1)}
if($account.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($accountName)){$account=@($localAccounts | Where-Object {$_.Name -ieq $accountName} | Select-Object -First 1)}
if($account.Count -eq 0){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_ACCOUNT_NOT_FOUND';ErrorMessage='The LAPS-managed local account identified by event evidence could not be found.'}}
$adminSids=@(Get-LocalAdministratorsMemberSids)
$isAdmin=([string]$account[0].SID -in $adminSids)
$isEnabled=(-not [bool]$account[0].Disabled)
if(-not $isEnabled){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_ACCOUNT_DISABLED';ErrorMessage='The LAPS-managed local account is disabled.'}}
if(-not $isAdmin){return [pscustomobject]@{Verified=$false;ErrorCode='LAPS_ACCOUNT_NOT_ADMIN';ErrorMessage='The LAPS-managed local account is not a member of local Administrators.'}}
[pscustomobject]@{
Verified=$true;AccountName="$env:COMPUTERNAME\$($account[0].Name)";AccountSid=[string]$account[0].SID
PolicySource=$policy.Source;VerificationLevel='EVENT_CONFIRMED_DIRECTORY_BACKUP_AND_LOCAL_UPDATE';AccountResolutionSource=$accountResolutionSource
LastPolicySuccessUtc=$policyEvent.TimeCreated.ToUniversalTime().ToString('o')
LastBackupSuccessUtc=$backupEvent.TimeCreated.ToUniversalTime().ToString('o')
LastLocalUpdateUtc=$localEvent.TimeCreated.ToUniversalTime().ToString('o')
ErrorCode=$null;ErrorMessage=$null
}
}
function Initialize-NativeTypes {
if ('EmergencySecurity.Native' -as [type]) { return }
$source = @'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace EmergencySecurity
{
public static class Native
{
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public IntPtr ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern uint LsaOpenPolicy(IntPtr SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, uint DesiredAccess, out IntPtr PolicyHandle);
[DllImport("advapi32.dll")]
private static extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights);
[DllImport("advapi32.dll")]
private static extern uint LsaRemoveAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, bool AllRights, LSA_UNICODE_STRING[] UserRights, uint CountOfRights);
[DllImport("advapi32.dll")]
private static extern uint LsaEnumerateAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, out IntPtr UserRights, out uint CountOfRights);
[DllImport("advapi32.dll")]
private static extern uint LsaNtStatusToWinError(uint Status);
[DllImport("advapi32.dll")]
private static extern uint LsaClose(IntPtr PolicyHandle);
[DllImport("advapi32.dll")]
private static extern uint LsaFreeMemory(IntPtr Buffer);
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
private static extern int NetLocalGroupGetMembers(string servername, string localgroupname, int level, out IntPtr bufptr, int prefmaxlen, out int entriesread, out int totalentries, ref IntPtr resume_handle);
[DllImport("Netapi32.dll")]
private static extern int NetApiBufferFree(IntPtr Buffer);
private const uint POLICY_LOOKUP_NAMES = 0x00000800;
private const uint POLICY_CREATE_ACCOUNT = 0x00000010;
private const uint STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034;
private const int MAX_PREFERRED_LENGTH = -1;
private const int ERROR_MORE_DATA = 234;
private static IntPtr OpenPolicy()
{
LSA_OBJECT_ATTRIBUTES loa = new LSA_OBJECT_ATTRIBUTES();
loa.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));
IntPtr handle;
uint status = LsaOpenPolicy(IntPtr.Zero, ref loa, POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, out handle);
if (status != 0)
throw new Win32Exception((int)LsaNtStatusToWinError(status));
return handle;
}
private static IntPtr SidToPtr(string sidValue)
{
SecurityIdentifier sid = new SecurityIdentifier(sidValue);
byte[] bytes = new byte[sid.BinaryLength];
sid.GetBinaryForm(bytes, 0);
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, ptr, bytes.Length);
return ptr;
}
private static LSA_UNICODE_STRING NewLsaString(string value, out IntPtr buffer)
{
buffer = Marshal.StringToHGlobalUni(value);
return new LSA_UNICODE_STRING
{
Length = (ushort)(value.Length * 2),
MaximumLength = (ushort)((value.Length + 1) * 2),
Buffer = buffer
};
}
public static string[] GetRights(string sidValue)
{
IntPtr policy = IntPtr.Zero;
IntPtr sid = IntPtr.Zero;
IntPtr rightsPtr = IntPtr.Zero;
try
{
policy = OpenPolicy();
sid = SidToPtr(sidValue);
uint count;
uint status = LsaEnumerateAccountRights(policy, sid, out rightsPtr, out count);
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
return new string[0];
if (status != 0)
throw new Win32Exception((int)LsaNtStatusToWinError(status));
List<string> rights = new List<string>();
int size = Marshal.SizeOf(typeof(LSA_UNICODE_STRING));
for (int i = 0; i < count; i++)
{
IntPtr itemPtr = IntPtr.Add(rightsPtr, i * size);
LSA_UNICODE_STRING item = (LSA_UNICODE_STRING)Marshal.PtrToStructure(itemPtr, typeof(LSA_UNICODE_STRING));
string text = Marshal.PtrToStringUni(item.Buffer, item.Length / 2);
rights.Add(text);
}
return rights.ToArray();
}
finally
{
if (rightsPtr != IntPtr.Zero) LsaFreeMemory(rightsPtr);
if (sid != IntPtr.Zero) Marshal.FreeHGlobal(sid);
if (policy != IntPtr.Zero) LsaClose(policy);
}
}
public static void AddRight(string sidValue, string right)
{
IntPtr policy = IntPtr.Zero;
IntPtr sid = IntPtr.Zero;
IntPtr textBuffer = IntPtr.Zero;
try
{
policy = OpenPolicy();
sid = SidToPtr(sidValue);
LSA_UNICODE_STRING item = NewLsaString(right, out textBuffer);
uint status = LsaAddAccountRights(policy, sid, new LSA_UNICODE_STRING[] { item }, 1);
if (status != 0)
throw new Win32Exception((int)LsaNtStatusToWinError(status));
}
finally
{
if (textBuffer != IntPtr.Zero) Marshal.FreeHGlobal(textBuffer);
if (sid != IntPtr.Zero) Marshal.FreeHGlobal(sid);
if (policy != IntPtr.Zero) LsaClose(policy);
}
}
public static void RemoveRight(string sidValue, string right)
{
IntPtr policy = IntPtr.Zero;
IntPtr sid = IntPtr.Zero;
IntPtr textBuffer = IntPtr.Zero;
try
{
policy = OpenPolicy();
sid = SidToPtr(sidValue);
LSA_UNICODE_STRING item = NewLsaString(right, out textBuffer);
uint status = LsaRemoveAccountRights(policy, sid, false, new LSA_UNICODE_STRING[] { item }, 1);
if (status != 0 && status != STATUS_OBJECT_NAME_NOT_FOUND)
throw new Win32Exception((int)LsaNtStatusToWinError(status));
}
finally
{
if (textBuffer != IntPtr.Zero) Marshal.FreeHGlobal(textBuffer);
if (sid != IntPtr.Zero) Marshal.FreeHGlobal(sid);
if (policy != IntPtr.Zero) LsaClose(policy);
}
}
public enum WTS_CONNECTSTATE_CLASS
{
Active, Connected, ConnectQuery, Shadow, Disconnected, Idle, Listen, Reset, Down, Init
}
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public int SessionID;
public IntPtr pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}
[DllImport("wtsapi32.dll", EntryPoint = "WTSEnumerateSessionsW", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr ppSessionInfo, out int pCount);
[DllImport("wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr pMemory);
[DllImport("wtsapi32.dll", EntryPoint = "WTSQuerySessionInformationW", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool WTSQuerySessionInformation(IntPtr hServer, int SessionId, int WTSInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
[DllImport("wtsapi32.dll", SetLastError = true)]
private static extern bool WTSLogoffSession(IntPtr hServer, int SessionId, bool bWait);
public class SessionInfo
{
public int SessionId { get; set; }
public string StationName { get; set; }
public string State { get; set; }
public string UserName { get; set; }
public string DomainName { get; set; }
}
private static string QuerySessionString(int sessionId, int infoClass)
{
IntPtr buffer = IntPtr.Zero;
int bytes = 0;
try
{
if (!WTSQuerySessionInformation(IntPtr.Zero, sessionId, infoClass, out buffer, out bytes) || buffer == IntPtr.Zero)
return String.Empty;
return Marshal.PtrToStringUni(buffer) ?? String.Empty;
}
finally
{
if (buffer != IntPtr.Zero) WTSFreeMemory(buffer);
}
}
public static SessionInfo[] GetSessions()
{
IntPtr buffer = IntPtr.Zero;
int count = 0;
List<SessionInfo> sessions = new List<SessionInfo>();
try
{
if (!WTSEnumerateSessions(IntPtr.Zero, 0, 1, out buffer, out count))
throw new Win32Exception(Marshal.GetLastWin32Error());
int size = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
for (int i = 0; i < count; i++)
{
IntPtr itemPtr = IntPtr.Add(buffer, i * size);
WTS_SESSION_INFO item = (WTS_SESSION_INFO)Marshal.PtrToStructure(itemPtr, typeof(WTS_SESSION_INFO));
sessions.Add(new SessionInfo
{
SessionId = item.SessionID,
StationName = Marshal.PtrToStringUni(item.pWinStationName) ?? String.Empty,
State = item.State.ToString(),
UserName = QuerySessionString(item.SessionID, 5),
DomainName = QuerySessionString(item.SessionID, 7)
});
}
return sessions.ToArray();
}
finally
{
if (buffer != IntPtr.Zero) WTSFreeMemory(buffer);
}
}
public static void LogoffSession(int sessionId)
{
if (!WTSLogoffSession(IntPtr.Zero, sessionId, true))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
public static string[] GetLocalGroupMemberSids(string groupName)
{
List<string> results = new List<string>();
IntPtr resume = IntPtr.Zero;
int status;
do
{
IntPtr buffer = IntPtr.Zero;
int entriesRead = 0;
int totalEntries = 0;
try
{
status = NetLocalGroupGetMembers(null, groupName, 0, out buffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, ref resume);
if (status != 0 && status != ERROR_MORE_DATA)
throw new Win32Exception(status);
for (int i = 0; i < entriesRead; i++)
{
IntPtr sidPtr = Marshal.ReadIntPtr(buffer, i * IntPtr.Size);
if (sidPtr != IntPtr.Zero)
results.Add(new SecurityIdentifier(sidPtr).Value);
}
}
finally
{
if (buffer != IntPtr.Zero) NetApiBufferFree(buffer);
}
} while (status == ERROR_MORE_DATA);
return results.ToArray();
}
}
}
'@
Add-Type -TypeDefinition $source -Language CSharp -ErrorAction Stop | Out-Null
}
function Get-SessionEvidence {
$sessions = @()
$profiles=@(Get-ProfileRegistryRecords)
$localSidPrefix=Get-LocalMachineSidPrefix
foreach ($session in @([EmergencySecurity.Native]::GetSessions())) {
$accountName = $null
$sid = $null
if (-not [string]::IsNullOrWhiteSpace($session.UserName)) {
$accountName = if ([string]::IsNullOrWhiteSpace($session.DomainName)) { $session.UserName } else { "$($session.DomainName)\$($session.UserName)" }
$isLocalSession=([string]$session.DomainName -ieq $env:COMPUTERNAME -or [string]::IsNullOrWhiteSpace([string]$session.DomainName))
$matches=@($profiles | Where-Object {
$_.leafName -ieq [string]$session.UserName -and (
($isLocalSession -and $localSidPrefix -and $_.sid -like "$localSidPrefix-*") -or
(-not $isLocalSession -and (-not $localSidPrefix -or $_.sid -notlike "$localSidPrefix-*"))
)
})
if($matches.Count -eq 1) {
$sid=[string]$matches[0].sid
}
elseif($isLocalSession) {
try {
$localName="$env:COMPUTERNAME\$($session.UserName)"
$sid=([System.Security.Principal.NTAccount]::new($localName)).Translate([System.Security.Principal.SecurityIdentifier]).Value
} catch { }
}
# For a domain session, avoid an online account translation fallback while the DC
# is unavailable. An interactive domain session normally has a ProfileList record.
}
$sessions += [pscustomobject][ordered]@{
sessionId = [int]$session.SessionId
stationName = [string]$session.StationName
state = [string]$session.State
accountName = $accountName
sid = $sid
}
}
return $sessions
}
function Get-TargetSessionIds {
param([object[]]$Sessions, [string[]]$TargetSids, [string]$PreservedSid)
$ids = @()
foreach ($session in $Sessions) {
if (-not $session.sid) { continue }
if ($PreservedSid -and $session.sid -eq $PreservedSid) { continue }
if ($session.sid -in $TargetSids) { $ids += [int]$session.sessionId }
}
return @($ids | Sort-Object -Unique)
}
function Get-ProcessEvidence {
param([int[]]$SessionIds)
if ($null -eq $SessionIds -or $SessionIds.Count -eq 0) { return @() }
$items = @()
try {
foreach ($process in @(Get-CimInstance Win32_Process -ErrorAction Stop | Where-Object { [int]$_.SessionId -in $SessionIds })) {
$items += [pscustomobject][ordered]@{
processId = [int]$process.ProcessId
parentId = [int]$process.ParentProcessId
sessionId = [int]$process.SessionId
name = [string]$process.Name
executable = [string]$process.ExecutablePath
commandLine = [string]$process.CommandLine
creationDate = if ($process.CreationDate) { ([datetime]$process.CreationDate).ToUniversalTime().ToString('o') } else { $null }
}
}
}
catch { throw }
return $items
}
function Get-IdentityMatchMaps {
param([object[]]$TargetIdentities)
$exact=@{};$short=@{}
foreach($identity in @($TargetIdentities | Where-Object {$_.resolveStatus -eq 'SUCCESS'})){
foreach($field in @('requested','normalized','accountName')){
$value=[string]$identity.$field
if([string]::IsNullOrWhiteSpace($value)){continue}
$normalizedName=Normalize-AccountName $value
$exact[$normalizedName.ToLowerInvariant()]=[string]$identity.sid
$leaf=if($normalizedName.Contains('\')){$normalizedName.Split('\')[-1]}else{$normalizedName}
$key=$leaf.ToLowerInvariant()
if(-not $short.ContainsKey($key)){$short[$key]=@()}
$short[$key]=@($short[$key]+[string]$identity.sid | Select-Object -Unique)
}
}
[pscustomobject]@{Exact=$exact;Short=$short}
}
function Resolve-EvidencePrincipalSid {
param([string]$Principal,$Maps)
if([string]::IsNullOrWhiteSpace($Principal)){return $null}
if($Principal -match '^S-\d-\d+(-\d+)+$'){return $Principal}
$normalizedPrincipal=Normalize-AccountName $Principal
$key=$normalizedPrincipal.ToLowerInvariant()
if($Maps.Exact.ContainsKey($key)){return [string]$Maps.Exact[$key]}
if(-not $normalizedPrincipal.Contains('\')){
$shortKey=$normalizedPrincipal.ToLowerInvariant()
if($Maps.Short.ContainsKey($shortKey) -and @($Maps.Short[$shortKey]).Count -eq 1){return [string]$Maps.Short[$shortKey][0]}
}
return $null
}
function Get-ScheduledTaskEvidence {
param([object[]]$TargetIdentities,[bool]$AllScope)
$items=@()
$targetSids=@($TargetIdentities | Where-Object {$_.resolveStatus -eq 'SUCCESS'} | ForEach-Object {[string]$_.sid})
$maps=Get-IdentityMatchMaps -TargetIdentities $TargetIdentities
try {
$scheduler=New-Object -ComObject 'Schedule.Service'
$scheduler.Connect()
$queue=New-Object System.Collections.Queue
$queue.Enqueue($scheduler.GetFolder('\'))
while($queue.Count -gt 0) {
$folder=$queue.Dequeue()
try {
$subFolders=$folder.GetFolders(0)
for($i=1;$i -le $subFolders.Count;$i++) {
$queue.Enqueue($subFolders.Item($i))
}
} catch { }
try {
$registeredTasks=$folder.GetTasks(0)
for($i=1;$i -le $registeredTasks.Count;$i++) {
$task=$registeredTasks.Item($i)
try {
[xml]$taskXml=$task.Xml
$principal=[string]$taskXml.Task.Principals.Principal.UserId
if([string]::IsNullOrWhiteSpace($principal)){continue}
if($principal -in @('SYSTEM','LOCAL SERVICE','NETWORK SERVICE','S-1-5-18','S-1-5-19','S-1-5-20')){continue}
if($principal -like 'NT SERVICE\*'){continue}
$principalSid=Resolve-EvidencePrincipalSid -Principal $principal -Maps $maps
$include=$AllScope -or ($principalSid -and $principalSid -in $targetSids)
if(-not $include){continue}
$stateName=switch([int]$task.State){0{'Unknown'}1{'Disabled'}2{'Queued'}3{'Ready'}4{'Running'}default{[string]$task.State}}
$fullPath=[string]$task.Path
$taskName=Split-Path -Path $fullPath -Leaf
$taskPath=if($fullPath.Length -gt $taskName.Length){$fullPath.Substring(0,$fullPath.Length-$taskName.Length)}else{'\'}
$items += [pscustomobject][ordered]@{
taskPath=$taskPath
taskName=$taskName
state=$stateName
principal=$principal
principalSid=$principalSid
logonType=[string]$taskXml.Task.Principals.Principal.LogonType
runLevel=[string]$taskXml.Task.Principals.Principal.RunLevel
}
} catch { }
}
} catch { }
}
}
catch {
# Evidence collection is best-effort and must never delay emergency enforcement by
# waiting on domain principal translation.
return @()
}
return $items
}
function Get-ServiceEvidence {
param([object[]]$TargetIdentities,[bool]$AllScope)
$items=@()
$targetSids=@($TargetIdentities | Where-Object {$_.resolveStatus -eq 'SUCCESS'} | ForEach-Object {[string]$_.sid})
$maps=Get-IdentityMatchMaps -TargetIdentities $TargetIdentities
foreach($service in @(Get-CimInstance Win32_Service -ErrorAction SilentlyContinue)){
$principal=[string]$service.StartName
if([string]::IsNullOrWhiteSpace($principal)){continue}
if($principal -in @('LocalSystem','NT AUTHORITY\LocalService','NT AUTHORITY\NetworkService','.\LocalSystem')){continue}
if($principal -like 'NT SERVICE\*'){continue}
$principalSid=Resolve-EvidencePrincipalSid -Principal $principal -Maps $maps
$include=$AllScope -or ($principalSid -and $principalSid -in $targetSids)
if(-not $include){continue}
$items += [pscustomobject][ordered]@{name=[string]$service.Name;displayName=[string]$service.DisplayName;state=[string]$service.State;startMode=[string]$service.StartMode;startName=$principal;principalSid=$principalSid;processId=[int]$service.ProcessId}
}
return $items
}
function Get-NetworkConnectionEvidence {
param([int[]]$ProcessIds)
$items = @()
if ($null -eq $ProcessIds -or $ProcessIds.Count -eq 0) { return $items }
if (-not (Get-Command Get-NetTCPConnection -ErrorAction SilentlyContinue)) { return $items }
foreach ($connection in @(Get-NetTCPConnection -ErrorAction SilentlyContinue | Where-Object { [int]$_.OwningProcess -in $ProcessIds })) {
$items += [pscustomobject][ordered]@{
owningProcess = [int]$connection.OwningProcess
localAddress = [string]$connection.LocalAddress
localPort = [int]$connection.LocalPort
remoteAddress = [string]$connection.RemoteAddress
remotePort = [int]$connection.RemotePort
state = [string]$connection.State
}
}
return $items
}
function Get-CachedLogonState {
$path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
$valueName = 'CachedLogonsCount'
$exists = $false
$value = $null
$kind = $null
try {
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon', $false)
if ($null -ne $key) {
$names = @($key.GetValueNames())
if ($valueName -in $names) {
$exists = $true
$value = [string]$key.GetValue($valueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
$kind = [string]$key.GetValueKind($valueName)
}
$key.Close()
}
}
catch { }
return [pscustomobject]@{ path=$path; valueName=$valueName; existed=$exists; value=$value; kind=$kind }
}
function Disable-CachedDomainLogon {
$path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
New-ItemProperty -Path $path -Name 'CachedLogonsCount' -Value '0' -PropertyType String -Force | Out-Null
$current = [string](Get-ItemProperty -Path $path -Name 'CachedLogonsCount' -ErrorAction Stop).CachedLogonsCount
if ($current -ne '0') { throw 'CachedLogonsCount verification failed.' }
}
function Get-NetlogonState {
$service = Get-CimInstance Win32_Service -Filter "Name='Netlogon'" -ErrorAction Stop
return [pscustomobject]@{
startMode = [string]$service.StartMode
state = [string]$service.State
}
}
function Disable-Netlogon {
$service = Get-Service -Name Netlogon -ErrorAction Stop
if ($service.Status -ne 'Stopped') { Stop-Service -Name Netlogon -Force -ErrorAction Stop }
Set-Service -Name Netlogon -StartupType Disabled -ErrorAction Stop
$current = Get-CimInstance Win32_Service -Filter "Name='Netlogon'" -ErrorAction Stop
if ($current.State -ne 'Stopped' -or $current.StartMode -ne 'Disabled') {
throw "Netlogon verification failed. State=$($current.State), StartMode=$($current.StartMode)"
}
}
function Get-KdcProxyState {
$rootPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos'
$mappingPath = Join-Path $rootPath 'KdcProxyServer'
$state = [ordered]@{
rootPath = $rootPath
rootExisted = Test-Path $rootPath
enabledValueExisted = $false
enabledValue = $null
mappingPathExisted = Test-Path $mappingPath
mappings = @()
}
if (Test-Path $rootPath) {
$item = Get-ItemProperty -Path $rootPath -ErrorAction SilentlyContinue
if ($null -ne $item -and $item.PSObject.Properties.Name -contains 'KdcProxyServer_Enabled') {
$state.enabledValueExisted = $true
$state.enabledValue = $item.KdcProxyServer_Enabled
}
}
if (Test-Path $mappingPath) {
$mapItem = Get-ItemProperty -Path $mappingPath -ErrorAction SilentlyContinue
if ($null -ne $mapItem) {
foreach ($property in @($mapItem.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' })) {
$state.mappings += [pscustomobject]@{ name=$property.Name; value=[string]$property.Value }
}
}
}
return [pscustomobject]$state
}
function Disable-KdcProxy {
$rootPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos'
$mappingPath = Join-Path $rootPath 'KdcProxyServer'
if (-not (Test-Path $rootPath)) { $null = New-Item -Path $rootPath -Force }
New-ItemProperty -Path $rootPath -Name 'KdcProxyServer_Enabled' -Value 0 -PropertyType DWord -Force | Out-Null
if (Test-Path $mappingPath) { Remove-Item -Path $mappingPath -Recurse -Force -ErrorAction Stop }
}
function Get-LocalDomainContext {
$computerSystem=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
$partOfDomain=[bool]$computerSystem.PartOfDomain
$domainName=if($partOfDomain){([string]$computerSystem.Domain).Trim().TrimEnd('.')}else{$null}
$fqdn=$null
try{$fqdn=([System.Net.Dns]::GetHostEntry($env:COMPUTERNAME).HostName).TrimEnd('.')}catch{}
$suffixes=@()
try{
foreach($adapter in @(Get-CimInstance Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True' -ErrorAction SilentlyContinue)){
if($adapter.DNSDomain){$suffixes += [string]$adapter.DNSDomain}
$suffixes += @($adapter.DNSDomainSuffixSearchOrder)
}
}catch{}
foreach($name in @('Domain','NV Domain','SearchList')){
try{$value=(Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -Name $name -ErrorAction Stop).$name;if($value){$suffixes += @([string]$value -split ',')}}catch{}
}
if($fqdn -and $fqdn.Contains('.')){$suffixes += $fqdn.Substring($fqdn.IndexOf('.')+1)}
$azureAdJoined=$false;$workplaceJoined=$false
try{
$dsreg=& (Join-Path $env:SystemRoot 'System32\dsregcmd.exe') /status 2>$null
foreach($line in @($dsreg)){
if([string]$line -match '^\s*AzureAdJoined\s*:\s*(YES|NO)\s*$'){$azureAdJoined=($matches[1] -eq 'YES')}
if([string]$line -match '^\s*WorkplaceJoined\s*:\s*(YES|NO)\s*$'){$workplaceJoined=($matches[1] -eq 'YES')}
}
}catch{}
[pscustomobject][ordered]@{
partOfDomain=$partOfDomain;domainName=$domainName;computerFqdn=$fqdn
dnsSuffixes=@($suffixes | ForEach-Object {$_.Trim().TrimEnd('.')} | Where-Object {$_} | Select-Object -Unique)
azureAdJoined=$azureAdJoined;workplaceJoined=$workplaceJoined
authoritativeSource='WIN32_COMPUTERSYSTEM';supplementalSources=@('COMPUTER_FQDN','ACTIVE_ADAPTER_DNS_SUFFIX','TCPIP_PARAMETERS','DSREGCMD')
}
}
function Get-ConfiguredDnsServerAddresses {
$addresses=@()
try {
if(Get-Command Get-DnsClientServerAddress -ErrorAction SilentlyContinue) {
foreach($item in @(Get-DnsClientServerAddress -ErrorAction Stop)) {
$addresses += @($item.ServerAddresses)
}
}
} catch { }
if($addresses.Count -eq 0) {
try {
foreach($adapter in @(Get-CimInstance Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True' -ErrorAction Stop)) {
$addresses += @($adapter.DNSServerSearchOrder)
}
} catch { }
}
$valid=@()
foreach($value in @($addresses)) {
if([string]::IsNullOrWhiteSpace([string]$value)){continue}
$ip=$null
if([System.Net.IPAddress]::TryParse([string]$value,[ref]$ip)) {
$text=$ip.IPAddressToString
if($text -notin @('0.0.0.0','127.0.0.1','::1') -and $text -notlike '169.254.*' -and $text -notlike 'fe80:*') {
$valid += $text
}
}
}
return @($valid | Select-Object -Unique)
}
function Resolve-DnsRecordsQuick {
param([string]$Name,[string]$Type)
if(-not (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue)){return @()}
$parameters=@{Name=$Name;Type=$Type;ErrorAction='Stop';DnsOnly=$true}
$command=Get-Command Resolve-DnsName -ErrorAction Stop
if($command.Parameters.ContainsKey('QuickTimeout')){$parameters['QuickTimeout']=$true}
try { return @(Resolve-DnsName @parameters) } catch { return @() }
}
function Invoke-NltestDcDiscovery {
param([string]$DomainName,[int]$TimeoutMilliseconds=5000)
$output=''
try {
$startInfo=[System.Diagnostics.ProcessStartInfo]::new()
$startInfo.FileName=Join-Path $env:SystemRoot 'System32\nltest.exe'
$startInfo.Arguments="/dsgetdc:$DomainName"
$startInfo.UseShellExecute=$false
$startInfo.CreateNoWindow=$true
$startInfo.RedirectStandardOutput=$true
$startInfo.RedirectStandardError=$true
$process=[System.Diagnostics.Process]::new()
$process.StartInfo=$startInfo
if(-not $process.Start()){return [pscustomobject]@{completed=$false;exitCode=$null;output=''}}
if(-not $process.WaitForExit($TimeoutMilliseconds)) {
try{$process.Kill()}catch{}
return [pscustomobject]@{completed=$false;exitCode=$null;output=''}
}
$output=$process.StandardOutput.ReadToEnd()
return [pscustomobject]@{completed=$true;exitCode=$process.ExitCode;output=$output}
} catch {
return [pscustomobject]@{completed=$false;exitCode=$null;output=''}
}
}
function Get-DomainControllerDiscovery {
param([string]$DomainName)
$names=@()
$sources=@()
$dnsSrvSucceeded=$false
$resolvedAddresses=@()
$dnsServerCandidates=@(Get-ConfiguredDnsServerAddresses)
if([string]::IsNullOrWhiteSpace($DomainName)){
return [pscustomobject]@{
domainName=$DomainName;dcNames=@();addresses=@();resolvedAddresses=@()
dnsServerCandidates=$dnsServerCandidates;sources=@();dnsSrvSucceeded=$false
onlineDiscovery=$false;addressConfidence='NONE'
}
}
foreach($query in @("_ldap._tcp.dc._msdcs.$DomainName","_kerberos._tcp.$DomainName")) {
$records=@(Resolve-DnsRecordsQuick -Name $query -Type 'SRV')
foreach($record in $records) {
if($record.NameTarget){$names += ([string]$record.NameTarget).TrimEnd('.')}
}
if($records.Count -gt 0){$dnsSrvSucceeded=$true}
}
if($dnsSrvSucceeded){$sources += 'DNS_SRV'}
# Local historical sources are fast and remain available while the device is away from a DC.
try {
$gp=Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History' -Name DCName -ErrorAction Stop
if($gp.DCName){$names += ([string]$gp.DCName).TrimStart('\').TrimEnd('.');$sources += 'GROUP_POLICY_HISTORY'}
} catch { }
if($env:LOGONSERVER) {
$candidate=([string]$env:LOGONSERVER).TrimStart('\').TrimEnd('.')
if($candidate -and $candidate -ne $env:COMPUTERNAME){$names += $candidate;$sources += 'LOGONSERVER'}
}
# Nltest is useful online but must never hold emergency enforcement for minutes.
$nltest=Invoke-NltestDcDiscovery -DomainName $DomainName -TimeoutMilliseconds 5000
if($nltest.completed) {
foreach($line in @([string]$nltest.output -split "`r?`n")) {
if([string]$line -match '(?i)DC:\s+\\+([^\s]+)'){$names += $matches[1].TrimEnd('.')}
}
if($nltest.exitCode -eq 0){$sources += 'NLTEST'}
} else {
$sources += 'NLTEST_TIMEOUT_OR_UNAVAILABLE'
}
$names=@($names | Where-Object {$_} | Select-Object -Unique)
# First use the DNS client cache because it can contain the last DC address while DNS/DC is offline.
try {
if(Get-Command Get-DnsClientCache -ErrorAction SilentlyContinue) {
foreach($entry in @(Get-DnsClientCache -ErrorAction Stop)) {
$entryName=[string](Get-ObjectPropertyValue -Object $entry -Name 'Entry' -DefaultValue '')
if([string]::IsNullOrWhiteSpace($entryName)){$entryName=[string](Get-ObjectPropertyValue -Object $entry -Name 'Name' -DefaultValue '')}
if($entryName.TrimEnd('.') -notin $names){continue}
$data=[string](Get-ObjectPropertyValue -Object $entry -Name 'Data' -DefaultValue '')
$ip=$null
if([System.Net.IPAddress]::TryParse($data,[ref]$ip)){$resolvedAddresses += $ip.IPAddressToString}
}
if($resolvedAddresses.Count -gt 0){$sources += 'DNS_CLIENT_CACHE'}
}
} catch { }
foreach($name in $names) {
foreach($recordType in @('A','AAAA')) {
foreach($record in @(Resolve-DnsRecordsQuick -Name $name -Type $recordType)) {
if($record.IPAddress){$resolvedAddresses += [string]$record.IPAddress}
}
}
}
if($resolvedAddresses.Count -gt 0){$sources += 'DNS_ADDRESS_RESOLUTION'}
$resolvedAddresses=@(
$resolvedAddresses |
Where-Object {$_ -and $_ -notlike '169.254.*' -and $_ -notlike 'fe80:*' -and $_ -ne '::1'} |
Select-Object -Unique
)
$addresses=@($resolvedAddresses)
$confidence='RESOLVED_DC'
if($addresses.Count -eq 0 -and $dnsServerCandidates.Count -gt 0) {
# In standard AD deployments the configured DNS servers are commonly DC-integrated DNS.
# Treat them only as scoped fallback candidates and record the lower confidence explicitly.
$addresses=@($dnsServerCandidates)
$sources += 'CONFIGURED_DNS_SERVER_CANDIDATE'
$confidence='DNS_SERVER_CANDIDATE'
}
elseif($addresses.Count -eq 0) {
$confidence='NONE'
}
[pscustomobject][ordered]@{
domainName=$DomainName
dcNames=$names
addresses=@($addresses)
resolvedAddresses=@($resolvedAddresses)
dnsServerCandidates=@($dnsServerCandidates)
sources=@($sources | Select-Object -Unique)
dnsSrvSucceeded=$dnsSrvSucceeded
onlineDiscovery=($resolvedAddresses.Count -gt 0)
addressConfidence=$confidence
}
}
function New-AuthFirewallPlan {
param($DomainContext,$DcDiscovery)
if(-not [bool]$DomainContext.partOfDomain){
return [pscustomobject][ordered]@{
mode='NOT_APPLICABLE';domainName=$null;dcNames=@();remoteAddresses=@()
discoverySources=@();addressConfidence='NONE';ruleDefinitions=@()
warningCode='DEVICE_NOT_DOMAIN_JOINED'
}
}
$tcpFixed=@('88','135','139','389','445','464','636','3268','3269','9389')
$udpFixed=@('88','137','138','389','464')
$definitions=@()
$mode=$null
$warning=$null
$remote=@($DcDiscovery.addresses)
$confidence=[string](Get-ObjectPropertyValue -Object $DcDiscovery -Name 'addressConfidence' -DefaultValue 'NONE')
if($remote.Count -gt 0 -and $confidence -eq 'RESOLVED_DC' -and [bool]$DcDiscovery.dnsSrvSucceeded) {
$mode='DC_SCOPED'
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-Fixed";protocol='TCP';remotePorts=$tcpFixed;remoteAddresses=$remote}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-UDP-Fixed";protocol='UDP';remotePorts=$udpFixed;remoteAddresses=$remote}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-RPCDynamic";protocol='TCP';remotePorts=@('49152-65535');remoteAddresses=$remote}
}
elseif($remote.Count -gt 0 -and $confidence -eq 'RESOLVED_DC') {
$mode='HYBRID_FALLBACK'
$warning='DC_DISCOVERY_PARTIAL_GLOBAL_FIXED_FALLBACK'
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-Fixed";protocol='TCP';remotePorts=$tcpFixed;remoteAddresses=@('Any')}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-UDP-Fixed";protocol='UDP';remotePorts=$udpFixed;remoteAddresses=@('Any')}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-RPCDynamic";protocol='TCP';remotePorts=@('49152-65535');remoteAddresses=$remote}
}
elseif($remote.Count -gt 0 -and $confidence -eq 'DNS_SERVER_CANDIDATE') {
$mode='DNS_SERVER_CANDIDATE_FALLBACK'
$warning='DC_ADDRESS_UNRESOLVED_USING_CONFIGURED_DNS_SERVERS'
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-Fixed";protocol='TCP';remotePorts=$tcpFixed;remoteAddresses=@('Any')}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-UDP-Fixed";protocol='UDP';remotePorts=$udpFixed;remoteAddresses=@('Any')}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-RPCDynamic";protocol='TCP';remotePorts=@('49152-65535');remoteAddresses=$remote}
}
else {
$mode='GLOBAL_FIXED_FALLBACK'
$warning='DC_DISCOVERY_FAILED_GLOBAL_FIXED_FALLBACK'
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-TCP-Fixed";protocol='TCP';remotePorts=$tcpFixed;remoteAddresses=@('Any')}
$definitions += [pscustomobject]@{name="$FirewallRulePrefix-UDP-Fixed";protocol='UDP';remotePorts=$udpFixed;remoteAddresses=@('Any')}
}
[pscustomobject][ordered]@{
mode=$mode
domainName=[string]$DomainContext.domainName
dcNames=@($DcDiscovery.dcNames)
remoteAddresses=$remote
discoverySources=@($DcDiscovery.sources)
addressConfidence=$confidence
ruleDefinitions=$definitions
warningCode=$warning
}
}
function Convert-ToNormalizedSet {
param($Value)
$items=@()
foreach($entry in @($Value)){if($null -ne $entry){$items += @(([string]$entry -split ',') | ForEach-Object {$_.Trim()} | Where-Object {$_})}}
return @($items | Sort-Object -Unique)
}
function Convert-ProtocolName {
param($Value)
$text=[string]$Value
switch($text.ToUpperInvariant()){'6'{'TCP'}'17'{'UDP'}default{$text.ToUpperInvariant()}}
}
function Apply-AuthFirewallRules {
param($Plan)
if(-not (Get-Command New-NetFirewallRule -ErrorAction SilentlyContinue)){throw 'New-NetFirewallRule is unavailable.'}
$engine=Get-FirewallEnforcementState
$script:State.actions.firewallEngine=[ordered]@{
serviceState=$engine.serviceState
enabledProfiles=@($engine.enabledProfiles)
disabledProfiles=@($engine.disabledProfiles)
verified=[bool]$engine.pass
}
if(-not $engine.pass){throw "Windows Firewall enforcement is unavailable. Service=$($engine.serviceState);EnabledProfiles=$($engine.enabledProfiles -join ',')"}
$definitions=@($Plan.ruleDefinitions)
foreach($definition in $definitions){if(Get-NetFirewallRule -Name ([string]$definition.name) -ErrorAction SilentlyContinue){throw "Emergency firewall rule already exists: $($definition.name)"}}
$created=@()
try{
foreach($definition in $definitions){
New-NetFirewallRule -Name ([string]$definition.name) -DisplayName ([string]$definition.name) -Group $FirewallRuleGroup -Direction Outbound -Action Block -Enabled True -Profile Any -Protocol ([string]$definition.protocol) -RemoteAddress @($definition.remoteAddresses) -RemotePort @($definition.remotePorts) | Out-Null
$rule=@(Get-NetFirewallRule -Name ([string]$definition.name) -ErrorAction Stop)
if($rule.Count -ne 1){throw "Firewall rule count verification failed for $($definition.name)."}
$port=Get-NetFirewallPortFilter -AssociatedNetFirewallRule $rule[0] -ErrorAction Stop
$addr=Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $rule[0] -ErrorAction Stop
$expectedProtocol=Convert-ProtocolName $definition.protocol;$actualProtocol=Convert-ProtocolName $port.Protocol
$expectedPorts=Convert-ToNormalizedSet $definition.remotePorts;$actualPorts=Convert-ToNormalizedSet $port.RemotePort
$expectedAddresses=Convert-ToNormalizedSet $definition.remoteAddresses;$actualAddresses=Convert-ToNormalizedSet $addr.RemoteAddress
if([string]$rule[0].Enabled -ne 'True' -or [string]$rule[0].Action -ne 'Block' -or [string]$rule[0].Direction -ne 'Outbound' -or $actualProtocol -ne $expectedProtocol -or ($actualPorts -join '|') -ne ($expectedPorts -join '|') -or ($actualAddresses -join '|') -ne ($expectedAddresses -join '|')){throw "Firewall effective-state verification failed for $($definition.name)."}
$created += [pscustomobject]@{name=[string]$definition.name;protocol=$actualProtocol;remotePort=$actualPorts;remoteAddress=$actualAddresses}
}
}catch{foreach($definition in $definitions){Remove-NetFirewallRule -Name ([string]$definition.name) -ErrorAction SilentlyContinue};throw}
return $created
}
function New-ScopeGuardIdentity {
param([string]$Sid,[string]$AccountName,[string]$Source)
[pscustomobject][ordered]@{
requested=$AccountName
normalized=$AccountName
sid=$Sid
accountName=$AccountName
isLocal=$false
profileFound=$false
profilePath=$null
resolutionMethod='SCOPE_GUARD_SID'
resolveStatus='SUCCESS'
errorCode=$null
errorMessage=$null
sources=@($Source)
persistenceEligible=$false
isScopeGuard=$true
}
}
function Get-AllScopeGuardIdentities {
param([object[]]$DiscoveredIdentities,[bool]$PreserveLaps)
$guards=@()
if(-not $PreserveLaps) {
# Microsoft documents that assigning Deny log on locally to Everyone prevents
# every account from signing in locally. We also apply the RDP and network deny
# rights through the same synthetic identity. Batch/service denies remain limited
# to individually discovered human identities.
$guards += New-ScopeGuardIdentity -Sid 'S-1-1-0' -AccountName 'Everyone' -Source 'ALL_SCOPE_EVERYONE_GUARD'
return $guards
}
# SaveLaps must not use Everyone because the preserved local LAPS account is also a
# member of Everyone. Instead, add broad current-domain group SIDs as a second layer
# beside DC/network isolation. Domain Users covers normal and newly created users;
# the additional privileged groups cover common administrative token paths.
$domainPrefixes=@(
$DiscoveredIdentities |
Where-Object {
-not [bool](Get-ObjectPropertyValue -Object $_ -Name 'isLocal' -DefaultValue $false) -and
[string]$_.sid -match '^S-1-5-21-\d+-\d+-\d+-\d+$'
} |
ForEach-Object { ([string]$_.sid -replace '-\d+$','') } |
Select-Object -Unique
)
foreach($prefix in $domainPrefixes) {
foreach($group in @(
@{Rid=513;Name='Domain Users'},
@{Rid=512;Name='Domain Admins'},
@{Rid=518;Name='Schema Admins'},
@{Rid=519;Name='Enterprise Admins'},
@{Rid=520;Name='Group Policy Creator Owners'},
@{Rid=525;Name='Protected Users'}
)) {
$guards += New-ScopeGuardIdentity -Sid "$prefix-$($group.Rid)" -AccountName "DOMAIN_SCOPE\$($group.Name)" -Source 'ALL_SAVELAPS_DOMAIN_GROUP_GUARD'
}
}
return @($guards | Group-Object sid | ForEach-Object {$_.Group | Select-Object -First 1})
}
function Get-FirewallEnforcementState {
$serviceState='UNKNOWN'
$enabledProfiles=@()
$disabledProfiles=@()
try {
$service=Get-CimInstance Win32_Service -Filter "Name='MpsSvc'" -ErrorAction Stop
$serviceState=[string]$service.State
} catch { }
try {
foreach($profile in @(Get-NetFirewallProfile -ErrorAction Stop)) {
if([bool]$profile.Enabled){$enabledProfiles += [string]$profile.Name}else{$disabledProfiles += [string]$profile.Name}
}
} catch { }
[pscustomobject][ordered]@{
serviceState=$serviceState
enabledProfiles=@($enabledProfiles)
disabledProfiles=@($disabledProfiles)
pass=($serviceState -eq 'Running' -and $enabledProfiles.Count -gt 0)
}
}
function Add-DenyRights {
param([object[]]$Identities,[bool]$AllScope)
$interactiveRights=@('SeDenyInteractiveLogonRight','SeDenyRemoteInteractiveLogonRight','SeDenyNetworkLogonRight')
$persistenceRights=@('SeDenyBatchLogonRight','SeDenyServiceLogonRight')
$records=@();$failures=@();$affected=0;$protectedIdentityCount=0
foreach($identity in @($Identities | Where-Object {$_.resolveStatus -eq 'SUCCESS'})){
$required=@($interactiveRights)
$persistenceEligible=(-not $AllScope) -or [bool](Get-ObjectPropertyValue -Object $identity -Name 'persistenceEligible' -DefaultValue $false)
if($persistenceEligible){$required += $persistenceRights}
try{$before=@([EmergencySecurity.Native]::GetRights([string]$identity.sid))}catch{$failures += [pscustomobject]@{sid=[string]$identity.sid;accountName=[string]$identity.accountName;right=$null;error="Unable to enumerate existing rights: $($_.Exception.Message)"};continue}
$record=[ordered]@{sid=[string]$identity.sid;accountName=[string]$identity.accountName;rightsBefore=$before;requiredRights=$required;rightsAdded=@();persistenceRightsApplied=$persistenceEligible}
foreach($right in $required){
if($right -in $before){continue}
try{[EmergencySecurity.Native]::AddRight([string]$identity.sid,$right);$record.rightsAdded += $right;$affected++}
catch{$failures += [pscustomobject]@{sid=[string]$identity.sid;accountName=[string]$identity.accountName;right=$right;error=$_.Exception.Message}}
}
$identityProtected=$true
try{$after=@([EmergencySecurity.Native]::GetRights([string]$identity.sid));foreach($right in $required){if($right -notin $after){$identityProtected=$false;$failures += [pscustomobject]@{sid=[string]$identity.sid;accountName=[string]$identity.accountName;right=$right;error='Post-change verification did not find the required deny right.'}}}}
catch{$identityProtected=$false;$failures += [pscustomobject]@{sid=[string]$identity.sid;accountName=[string]$identity.accountName;right=$null;error="Post-change right enumeration failed: $($_.Exception.Message)"}}
$record['verificationComplete']=$identityProtected;if($identityProtected){$protectedIdentityCount++}
$records += [pscustomobject]$record;$script:State.originalState.userRights=$records;Write-StateCheckpoint
}
[pscustomobject]@{Records=$records;Failures=$failures;AffectedCount=$affected;ProtectedIdentityCount=$protectedIdentityCount}
}
function Install-PostBootVerifier {
param([Parameter(Mandatory = $true)][ValidateSet('LOCKDOWN','RESTORE')][string]$Operation)
$bytes=[Convert]::FromBase64String($PostBootVerifierBase64)
[System.IO.File]::WriteAllBytes($PostBootVerifierPath,$bytes);Set-SecureFileAcl -Path $PostBootVerifierPath
$aclCheck=Test-SecurePathAcl -Path $PostBootVerifierPath
if(-not $aclCheck.Pass){throw "Post-boot verifier ACL verification failed. Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected);Unexpected=$($aclCheck.Unexpected)"}
$powerShellExe=Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
$taskCommand='"{0}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{1}"' -f $powerShellExe,$PostBootVerifierPath
& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Create /TN $PostBootTaskName /SC ONSTART /RU SYSTEM /RL HIGHEST /TR $taskCommand /F | Out-Null
if($LASTEXITCODE -ne 0){throw "schtasks.exe returned exit code $LASTEXITCODE while registering post-boot verification."}
& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Query /TN $PostBootTaskName | Out-Null
if($LASTEXITCODE -ne 0){throw 'Post-boot verification task could not be queried after creation.'}
$pending=[pscustomobject][ordered]@{operation=$Operation;status='PENDING';overallStatus='PENDING';scheduledUtc=Get-UtcTimestamp;verifiedUtc=$null;rebootVerified=$false;passedCheckCount=0;failedCheckCount=0;failedChecks='';taskName=$PostBootTaskName;scriptPath=$PostBootVerifierPath;checks=@()}
if($script:State.PSObject.Properties.Name -contains 'postBootVerification'){$script:State.postBootVerification=$pending}else{$script:State | Add-Member -NotePropertyName postBootVerification -NotePropertyValue $pending}
Write-StateCheckpoint
}
function Cancel-PostBootVerifier {
param([string]$Reason)
try{& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Delete /TN $PostBootTaskName /F | Out-Null}catch{}
if($script:State.PSObject.Properties.Name -contains 'postBootVerification'){$script:State.postBootVerification.status='CANCELED';$script:State.postBootVerification.overallStatus='CANCELED';if($script:State.postBootVerification.PSObject.Properties.Name -contains 'message'){$script:State.postBootVerification.message=$Reason}else{$script:State.postBootVerification | Add-Member -NotePropertyName message -NotePropertyValue $Reason}}
Write-StateCheckpoint
}
function Remove-EmergencyTaskByName {
param([string]$Name)
try { & (Join-Path $env:SystemRoot 'System32\schtasks.exe') /End /TN $Name 2>$null | Out-Null } catch { }
try { & (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Delete /TN $Name /F 2>$null | Out-Null } catch { }
}
function Remove-LegacyEmergencyTasks {
foreach ($name in @('EmergencyLockdown-PostBootVerify-v13','EmergencyLockdown-PostBootVerify-v131','EmergencyLockdown-PostBootVerify-v132','EmergencyLockdown-PostBootVerify-v133','EmergencyLockdown-PostBootVerify-v134','EmergencyLockdown-PostBootVerify-v135','EmergencyLockdown-DisruptionWorker-v132','EmergencyLockdown-DisruptionWorker-v133','EmergencyLockdown-DisruptionWorker-v134','EmergencyLockdown-DisruptionWorker-v135')) { Remove-EmergencyTaskByName -Name $name }
}
function Install-DisruptionWorker {
Set-SecureDirectoryAcl -Path $BaseDirectory
$bytes = [Convert]::FromBase64String($DisruptionWorkerBase64)
[System.IO.File]::WriteAllBytes($DisruptionWorkerPath,$bytes)
Set-SecureFileAcl -Path $DisruptionWorkerPath
$aclCheck = Test-SecurePathAcl -Path $DisruptionWorkerPath
if (-not $aclCheck.Pass) { throw "Disruption worker ACL verification failed. Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected);Unexpected=$($aclCheck.Unexpected)" }
Import-Module ScheduledTasks -ErrorAction Stop
Unregister-ScheduledTask -TaskName $DisruptionTaskName -Confirm:$false -ErrorAction SilentlyContinue
$powerShellExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
$arguments = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{0}"' -f $DisruptionWorkerPath
$action = New-ScheduledTaskAction -Execute $powerShellExe -Argument $arguments
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(1)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $DisruptionTaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
$task = Get-ScheduledTask -TaskName $DisruptionTaskName -ErrorAction Stop
if ($null -eq $task) { throw 'Disruption worker task could not be queried after creation.' }
$script:State.actions.disruptionWorker.installed = $true
Write-StateCheckpoint
}
function Start-DisruptionWorker {
Start-ScheduledTask -TaskName $DisruptionTaskName -ErrorAction Stop
Start-Sleep -Milliseconds 500
$task = Get-ScheduledTask -TaskName $DisruptionTaskName -ErrorAction Stop
$script:State.actions.disruptionWorker.started = $true
Write-StateCheckpoint
return [string]$task.State
}
function Cancel-DisruptionWorker {
param([string]$Reason)
try { Stop-ScheduledTask -TaskName $DisruptionTaskName -ErrorAction SilentlyContinue } catch { }
try { Unregister-ScheduledTask -TaskName $DisruptionTaskName -Confirm:$false -ErrorAction SilentlyContinue } catch { }
try { Remove-Item -LiteralPath $DisruptionWorkerPath -Force -ErrorAction SilentlyContinue } catch { }
if ($script:State -and $script:State.actions -and $script:State.actions.disruptionWorker) {
$script:State.actions.disruptionWorker.started = $false
}
Write-StateCheckpoint
}
function Schedule-Reboot {
$os=Get-CimInstance Win32_OperatingSystem -ErrorAction Stop
$script:State.actions.bootTimeBefore=([datetime]$os.LastBootUpTime).ToUniversalTime().ToString('o')
$arguments=@('/r','/f','/t',[string]$RebootDelaySeconds,'/c','"Emergency access lockdown: mandatory restart"')
$process=Start-Process -FilePath "$env:SystemRoot\System32\shutdown.exe" -ArgumentList $arguments -Wait -PassThru -WindowStyle Hidden
if($process.ExitCode -ne 0){throw "shutdown.exe returned exit code $($process.ExitCode)."}
$script:State.actions.rebootScheduled=$true;$script:State.actions.rebootScheduledUtc=Get-UtcTimestamp
}
# Main execution
try {
$stageStart=[DateTime]::UtcNow
try{
Set-SecureDirectoryAcl -Path $BaseDirectory
$aclCheck=Test-SecurePathAcl -Path $BaseDirectory
if(-not $aclCheck.Pass){throw "ACL verification failed. Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected);Unexpected=$($aclCheck.Unexpected)"}
Add-StageResult -Name 'SECURE_STATE_STORAGE' -Status 'SUCCESS' -Started $stageStart -Expected 'Owner SYSTEM; inheritance disabled; only SYSTEM and Administrators' -Actual "Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected)"
}catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'SECURE_STATE_STORAGE' -Status 'FAILED' -Started $stageStart -ErrorCode 'STATE_STORAGE_ACL_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 10}
$stageStart=[DateTime]::UtcNow
if(Test-Path -LiteralPath $StatePath){
try{$existing=Get-Content -LiteralPath $StatePath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop;if($existing.status -in @('APPLYING','ACTIVE_DISRUPTION_PENDING','ACTIVE_PENDING_REBOOT','ACTIVE','ACTIVE_VERIFIED','ACTIVE_VERIFIED_WITH_WARNINGS','LOCKDOWN_PARTIAL','RESTORING','RESTORED_PENDING_REBOOT','RESTORE_PARTIAL','UNHANDLED_FAILURE')){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'CHECK_EXISTING_STATE' -Status 'FAILED' -Started $stageStart -ErrorCode 'LOCKDOWN_ALREADY_ACTIVE' -ErrorMessage "Existing state status is $($existing.status). Restore or resolve it before a new lockdown.";Write-Remarks;exit 23}}
catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'CHECK_EXISTING_STATE' -Status 'FAILED' -Started $stageStart -ErrorCode 'EXISTING_STATE_UNREADABLE' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 24}
}
Add-StageResult -Name 'CHECK_EXISTING_STATE' -Status 'SUCCESS' -Started $stageStart
$script:StateWriteEnabled=$true;Write-StateCheckpoint
$stageStart=[DateTime]::UtcNow
$identity=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal=[System.Security.Principal.WindowsPrincipal]::new($identity)
$isSystem=($identity.User.Value -eq 'S-1-5-18')
$isAdministrator=$principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
$executionSessionId=[System.Diagnostics.Process]::GetCurrentProcess().SessionId
$script:State.actions.executionContext=[ordered]@{identityName=[string]$identity.Name;identitySid=[string]$identity.User.Value;processSessionId=[int]$executionSessionId;isSystem=[bool]$isSystem;isAdministrator=[bool]$isAdministrator}
if(-not ($isSystem -or $isAdministrator)){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'PRECHECK_ENVIRONMENT' -Status 'FAILED' -Started $stageStart -ErrorCode 'ELEVATION_REQUIRED' -ErrorMessage 'The script must run as SYSTEM or an elevated administrator.';Write-Remarks;exit 11}
try{Initialize-NativeTypes;Remove-LegacyEmergencyTasks;Add-StageResult -Name 'PRECHECK_ENVIRONMENT' -Status 'SUCCESS' -Started $stageStart -Expected 'SYSTEM/elevated context and native APIs available' -Actual "Identity=$($identity.Name);SID=$($identity.User.Value);SessionId=$executionSessionId"}catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'PRECHECK_ENVIRONMENT' -Status 'FAILED' -Started $stageStart -ErrorCode 'NATIVE_API_INITIALIZATION_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 12}
$stageStart=[DateTime]::UtcNow;$tokens=@()
if(-not [string]::IsNullOrWhiteSpace($TargetUsers)){$tokens=@($TargetUsers.Split(';') | ForEach-Object {$_.Trim()} | Where-Object {-not [string]::IsNullOrWhiteSpace($_)} | Select-Object -Unique)}
if($tokens.Count -eq 0){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'VALIDATE_PARAMETERS' -Status 'FAILED' -Started $stageStart -ErrorCode 'TARGETUSERS_REQUIRED' -ErrorMessage 'TargetUsers must be supplied and cannot be empty.';Write-Remarks;exit 20}
$allTokens=@($tokens | Where-Object {$_ -ieq 'ALL'})
if($allTokens.Count -gt 0 -and $tokens.Count -gt 1){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'VALIDATE_PARAMETERS' -Status 'FAILED' -Started $stageStart -ErrorCode 'ALL_CANNOT_BE_COMBINED' -ErrorMessage 'ALL cannot be combined with explicit account names.';Write-Remarks;exit 21}
if($SaveLaps -and -not ($tokens.Count -eq 1 -and $tokens[0] -ieq 'ALL')){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'VALIDATE_PARAMETERS' -Status 'FAILED' -Started $stageStart -ErrorCode 'SAVELAPS_REQUIRES_ALL' -ErrorMessage 'SaveLaps can only be used with TargetUsers ALL.';Write-Remarks;exit 22}
$script:State.requestedTargets=$tokens;Add-StageResult -Name 'VALIDATE_PARAMETERS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $tokens.Count -Expected 'Valid TargetUsers and SaveLaps combination' -Actual ($tokens -join ';')
$stageStart=[DateTime]::UtcNow;$allScope=($tokens.Count -eq 1 -and $tokens[0] -ieq 'ALL')
$script:State.scope=if($allScope){if($SaveLaps){'ALL_USERS_SAVE_LAPS'}else{'ALL_USERS'}}else{'EXPLICIT_USERS'}
Add-StageResult -Name 'DETERMINE_SCOPE' -Status 'SUCCESS' -Started $stageStart -Actual $script:State.scope
$preservedLapsSid=$null;$stageStart=[DateTime]::UtcNow
if($SaveLaps){
try{$laps=Get-VerifiedLapsAccount;if(-not $laps.Verified){$script:State.laps.validationError=$laps.ErrorCode;$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DETECT_LAPS' -Status 'FAILED' -Started $stageStart -ErrorCode $laps.ErrorCode -ErrorMessage $laps.ErrorMessage;Write-Remarks;exit 25}
$existingLapsRights=@([EmergencySecurity.Native]::GetRights([string]$laps.AccountSid))
$blockingLapsRights=@($existingLapsRights | Where-Object {$_ -in @('SeDenyInteractiveLogonRight','SeDenyRemoteInteractiveLogonRight','SeDenyNetworkLogonRight')})
if($blockingLapsRights.Count -gt 0){$script:State.laps.blockingDenyRights=$blockingLapsRights;$script:State.laps.validationError='LAPS_ACCOUNT_ALREADY_DENIED';$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DETECT_LAPS' -Status 'FAILED' -Started $stageStart -ErrorCode 'LAPS_ACCOUNT_ALREADY_DENIED' -ErrorMessage ("The verified LAPS account already has blocking deny rights: " + ($blockingLapsRights -join ','));Write-Remarks;exit 25}
$script:State.laps.detected=$true;$script:State.laps.verified=$true;$script:State.laps.accountName=$laps.AccountName;$script:State.laps.accountSid=$laps.AccountSid;$script:State.laps.policySource=$laps.PolicySource;$script:State.laps.verificationLevel=$laps.VerificationLevel;$script:State.laps.accountResolutionSource=$laps.AccountResolutionSource;$script:State.laps.lastPolicySuccessUtc=$laps.LastPolicySuccessUtc;$script:State.laps.lastBackupSuccessUtc=$laps.LastBackupSuccessUtc;$script:State.laps.lastLocalUpdateUtc=$laps.LastLocalUpdateUtc;$script:State.laps.blockingDenyRights=@();$preservedLapsSid=$laps.AccountSid
Add-StageResult -Name 'DETECT_LAPS' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected 'Configured, backed up, enabled local administrator account without blocking deny rights' -Actual "$($laps.AccountName);$($laps.VerificationLevel);BlockingDenyRights=0"}
catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DETECT_LAPS' -Status 'FAILED' -Started $stageStart -ErrorCode 'LAPS_DETECTION_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 26}
}else{Add-StageResult -Name 'DETECT_LAPS' -Status 'NOT_APPLICABLE' -Started $stageStart}
$targetIdentities=@();$stageStart=[DateTime]::UtcNow
if(-not $allScope){
foreach($token in $tokens){$targetIdentities += Resolve-AccountIdentity -RequestedName $token}
$script:State.targets=$targetIdentities;$resolvedCount=@($targetIdentities | Where-Object {$_.resolveStatus -eq 'SUCCESS'}).Count;$failedCount=$targetIdentities.Count-$resolvedCount
if($resolvedCount -eq 0){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'RESOLVE_TARGETS' -Status 'FAILED' -Started $stageStart -ErrorCode 'NO_TARGET_RESOLVED' -ErrorMessage 'None of the requested accounts could be resolved. No security change was applied.';Write-Remarks;exit 27}
elseif($failedCount -gt 0){Add-StageResult -Name 'RESOLVE_TARGETS' -Status 'WARNING' -Started $stageStart -AffectedCount $resolvedCount -ErrorCode 'SOME_TARGETS_NOT_RESOLVED' -ErrorMessage "$failedCount requested account(s) could not be resolved." -Expected "$($tokens.Count) resolved" -Actual "$resolvedCount resolved"}
else{Add-StageResult -Name 'RESOLVE_TARGETS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $resolvedCount -Expected "$($tokens.Count) resolved" -Actual "$resolvedCount resolved"}
Add-StageResult -Name 'DISCOVER_ALL_USERS' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow)
}else{
Add-StageResult -Name 'RESOLVE_TARGETS' -Status 'NOT_APPLICABLE' -Started $stageStart;$stageStart=[DateTime]::UtcNow
try{$targetIdentities=@(Get-DiscoveredHumanAccounts);if($preservedLapsSid){$targetIdentities=@($targetIdentities | Where-Object {$_.sid -ne $preservedLapsSid})};if($targetIdentities.Count -eq 0){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DISCOVER_ALL_USERS' -Status 'FAILED' -Started $stageStart -ErrorCode 'NO_HUMAN_ACCOUNTS_DISCOVERED' -ErrorMessage 'No human user account was discovered for ALL scope.';Write-Remarks;exit 28};$script:State.targets=$targetIdentities;Add-StageResult -Name 'DISCOVER_ALL_USERS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $targetIdentities.Count -Expected 'At least one non-system identity' -Actual "$($targetIdentities.Count) discovered"}
catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DISCOVER_ALL_USERS' -Status 'FAILED' -Started $stageStart -ErrorCode 'USER_DISCOVERY_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 29}
}
$targetSids=@($targetIdentities | Where-Object {$_.resolveStatus -eq 'SUCCESS'} | ForEach-Object {$_.sid} | Select-Object -Unique)
$enforcementIdentities=@($targetIdentities)
$scopeGuardIdentities=@()
$stageStart=[DateTime]::UtcNow
if($allScope) {
try {
$scopeGuardIdentities=@(Get-AllScopeGuardIdentities -DiscoveredIdentities $targetIdentities -PreserveLaps ([bool]$SaveLaps))
if((-not [bool]$SaveLaps) -and @($scopeGuardIdentities | Where-Object {$_.sid -eq 'S-1-1-0'}).Count -ne 1) {
throw 'The mandatory Everyone scope guard could not be prepared.'
}
if([bool]$SaveLaps -and $script:State.actions.domainContext -and [bool]$script:State.actions.domainContext.partOfDomain -and $scopeGuardIdentities.Count -eq 0) {
Add-WarningRecord -Code 'SAVELAPS_DOMAIN_GROUP_GUARD_UNAVAILABLE' -Message 'No cached domain SID was available for broad domain group guards. Network authentication isolation remains mandatory.'
}
$enforcementIdentities=@($targetIdentities + $scopeGuardIdentities)
$script:State.actions.scopeGuard.mode=if([bool]$SaveLaps){'DOMAIN_GROUPS_PLUS_NETWORK'}else{'EVERYONE'}
$script:State.actions.scopeGuard.status='PREPARED'
$script:State.actions.scopeGuard.principals=@($scopeGuardIdentities | ForEach-Object {[pscustomobject]@{sid=$_.sid;accountName=$_.accountName;source=$_.sources[0]}})
Add-StageResult -Name 'PREPARE_SCOPE_GUARD' -Status 'SUCCESS' -Started $stageStart -AffectedCount $scopeGuardIdentities.Count -Expected $(if([bool]$SaveLaps){'Broad domain guards without denying the preserved LAPS account'}else{'Everyone interactive/RDP/network guard'}) -Actual "$($scopeGuardIdentities.Count) guard principal(s)"
}
catch {
$script:State.status='FAILED_PRECHECK'
Add-StageResult -Name 'PREPARE_SCOPE_GUARD' -Status 'FAILED' -Started $stageStart -ErrorCode 'SCOPE_GUARD_PREPARATION_FAILED' -ErrorMessage $_.Exception.Message
Write-Remarks
exit 30
}
} else {
Add-StageResult -Name 'PREPARE_SCOPE_GUARD' -Status 'NOT_APPLICABLE' -Started $stageStart
}
$stageStart=[DateTime]::UtcNow
try{$sessions=@(Get-SessionEvidence);$script:State.evidence.sessions=$sessions;$sessionIds=@(Get-TargetSessionIds -Sessions $sessions -TargetSids $targetSids -PreservedSid $preservedLapsSid);$script:State.actions.sessionTelemetry.targetSessionsFound=$sessionIds.Count
if($allScope){
foreach($identity in $targetIdentities){
$matchingSession=@($sessions | Where-Object {$_.sid -and $_.sid -eq $identity.sid} | Select-Object -First 1)
if($matchingSession.Count -gt 0){
$identity.persistenceEligible=$true
$identity.sources=@($identity.sources + 'ACTIVE_SESSION' | Select-Object -Unique)
if(-not [string]::IsNullOrWhiteSpace([string]$matchingSession[0].accountName)){
$identity.accountName=[string]$matchingSession[0].accountName
$identity.normalized=[string]$matchingSession[0].accountName
}
}
}
$script:State.targets=$targetIdentities
}
Add-StageResult -Name 'CAPTURE_SESSIONS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $sessionIds.Count}
catch{$sessions=@();$sessionIds=@();Add-StageResult -Name 'CAPTURE_SESSIONS' -Status 'WARNING' -Started $stageStart -ErrorCode 'SESSION_CAPTURE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow;try{$processes=@(Get-ProcessEvidence -SessionIds $sessionIds);$script:State.evidence.processes=$processes;Add-StageResult -Name 'CAPTURE_PROCESSES' -Status 'SUCCESS' -Started $stageStart -AffectedCount $processes.Count}catch{$processes=@();Add-StageResult -Name 'CAPTURE_PROCESSES' -Status 'WARNING' -Started $stageStart -ErrorCode 'PROCESS_CAPTURE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow;try{$tasks=@(Get-ScheduledTaskEvidence -TargetIdentities $targetIdentities -AllScope $allScope);$script:State.evidence.scheduledTasks=$tasks;Add-StageResult -Name 'CAPTURE_TASKS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $tasks.Count}catch{Add-StageResult -Name 'CAPTURE_TASKS' -Status 'WARNING' -Started $stageStart -ErrorCode 'TASK_CAPTURE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow;try{$services=@(Get-ServiceEvidence -TargetIdentities $targetIdentities -AllScope $allScope);$script:State.evidence.services=$services;Add-StageResult -Name 'CAPTURE_SERVICES' -Status 'SUCCESS' -Started $stageStart -AffectedCount $services.Count}catch{Add-StageResult -Name 'CAPTURE_SERVICES' -Status 'WARNING' -Started $stageStart -ErrorCode 'SERVICE_CAPTURE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow;try{$processIds=@($script:State.evidence.processes | ForEach-Object {[int]$_.processId});$connections=@(Get-NetworkConnectionEvidence -ProcessIds $processIds);$script:State.evidence.networkConnections=$connections;Add-StageResult -Name 'CAPTURE_NETWORK_CONNECTIONS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $connections.Count}catch{Add-StageResult -Name 'CAPTURE_NETWORK_CONNECTIONS' -Status 'WARNING' -Started $stageStart -ErrorCode 'NETWORK_CAPTURE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow
if($allScope){
try{$domainContext=Get-LocalDomainContext;$script:State.actions.domainContext=$domainContext
if((-not $domainContext.partOfDomain) -and $domainContext.azureAdJoined){$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DISCOVER_DOMAIN_CONTEXT' -Status 'FAILED' -Started $stageStart -ErrorCode 'ENTRA_ONLY_ALL_SCOPE_UNSUPPORTED' -ErrorMessage 'ALL scope cannot guarantee full sign-in isolation on an Entra-only device in V1.4.0. No security change was applied.';Write-Remarks;exit 31}
if($domainContext.partOfDomain){
$dcDiscovery=Get-DomainControllerDiscovery -DomainName $domainContext.domainName
$plan=New-AuthFirewallPlan -DomainContext $domainContext -DcDiscovery $dcDiscovery
$script:State.actions.authFirewall=$plan
if($plan.warningCode){
$warningMessage=switch([string]$plan.warningCode){
'DC_ADDRESS_UNRESOLVED_USING_CONFIGURED_DNS_SERVERS' {'DC address resolution was unavailable. Configured DNS server addresses are used as scoped dynamic-RPC candidates while fixed AD ports are blocked globally.'}
'DC_DISCOVERY_PARTIAL_GLOBAL_FIXED_FALLBACK' {'DC addresses were obtained from local or partial sources. Fixed AD ports are blocked globally and dynamic RPC is blocked to the known addresses.'}
default {'No DC or DNS-server candidate address was available. Fixed AD ports are blocked globally; broad ALL-scope user-right guards remain the primary sign-in control.'}
}
Add-StageResult -Name 'DISCOVER_DOMAIN_CONTEXT' -Status 'WARNING' -Started $stageStart -ErrorCode $plan.warningCode -ErrorMessage $warningMessage -Expected 'Domain and DC addresses where available' -Actual "$($domainContext.domainName);Mode=$($plan.mode);IPs=$(@($plan.remoteAddresses).Count)"
}else{
Add-StageResult -Name 'DISCOVER_DOMAIN_CONTEXT' -Status 'SUCCESS' -Started $stageStart -Expected 'Domain and DC addresses' -Actual "$($domainContext.domainName);DCs=$(@($plan.dcNames).Count);IPs=$(@($plan.remoteAddresses).Count)"
}
}
else{$script:State.actions.authFirewall=New-AuthFirewallPlan -DomainContext $domainContext -DcDiscovery $null;Add-StageResult -Name 'DISCOVER_DOMAIN_CONTEXT' -Status 'NOT_APPLICABLE' -Started $stageStart -Message 'Device is not domain joined.'}}
catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'DISCOVER_DOMAIN_CONTEXT' -Status 'FAILED' -Started $stageStart -ErrorCode 'DOMAIN_CONTEXT_DISCOVERY_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 29}
}else{Add-StageResult -Name 'DISCOVER_DOMAIN_CONTEXT' -Status 'NOT_APPLICABLE' -Started $stageStart}
$stageStart=[DateTime]::UtcNow
if($allScope -and [bool]$SaveLaps -and $script:State.actions.domainContext -and [bool]$script:State.actions.domainContext.partOfDomain) {
$hasDomainGroupGuard=($scopeGuardIdentities.Count -gt 0)
$hasScopedDynamicRpc=@($script:State.actions.authFirewall.ruleDefinitions | Where-Object {$_.name -like '*RPCDynamic' -and @($_.remoteAddresses).Count -gt 0}).Count -gt 0
if(-not $hasDomainGroupGuard -or -not $hasScopedDynamicRpc) {
$script:State.status='FAILED_PRECHECK'
Add-StageResult -Name 'VALIDATE_SAVELAPS_ISOLATION' -Status 'FAILED' -Started $stageStart -ErrorCode 'SAVELAPS_FULL_DOMAIN_ISOLATION_NOT_VERIFIED' -ErrorMessage 'SaveLaps requires both cached domain-group guard SIDs and a scoped dynamic-RPC firewall target. No security change was applied.' -Expected 'Domain group guards and scoped dynamic RPC rule' -Actual "DomainGuards=$($scopeGuardIdentities.Count);ScopedDynamicRpc=$hasScopedDynamicRpc"
Write-Remarks
exit 31
}
Add-StageResult -Name 'VALIDATE_SAVELAPS_ISOLATION' -Status 'SUCCESS' -Started $stageStart -Expected 'Domain group guards and scoped dynamic RPC rule' -Actual "DomainGuards=$($scopeGuardIdentities.Count);ScopedDynamicRpc=$hasScopedDynamicRpc"
} else {
Add-StageResult -Name 'VALIDATE_SAVELAPS_ISOLATION' -Status 'NOT_APPLICABLE' -Started $stageStart
}
$stageStart=[DateTime]::UtcNow
try{$script:State.originalState.cachedLogonsCount=Get-CachedLogonState;if($allScope -and [bool]$script:State.actions.domainContext.partOfDomain){$script:State.originalState.netlogon=Get-NetlogonState;$script:State.originalState.kdcProxy=Get-KdcProxyState};Add-StageResult -Name 'CAPTURE_ORIGINAL_SECURITY_STATE' -Status 'SUCCESS' -Started $stageStart}
catch{$script:State.status='FAILED_PRECHECK';Add-StageResult -Name 'CAPTURE_ORIGINAL_SECURITY_STATE' -Status 'FAILED' -Started $stageStart -ErrorCode 'SECURITY_STATE_CAPTURE_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 30}
$script:State.status='APPLYING';Write-StateCheckpoint
$stageStart=[DateTime]::UtcNow
try{
$rightsResult=Add-DenyRights -Identities $enforcementIdentities -AllScope $allScope
$script:State.originalState.userRights=$rightsResult.Records
$script:State.actions.protectedIdentityCount=[int]$rightsResult.ProtectedIdentityCount
$guardSids=@($scopeGuardIdentities | ForEach-Object {[string]$_.sid})
$guardRecords=@($rightsResult.Records | Where-Object {[string]$_.sid -in $guardSids})
$guardVerified=@($guardRecords | Where-Object {[bool]$_.verificationComplete}).Count
if($allScope){
$script:State.actions.scopeGuard.verifiedCount=$guardVerified
$script:State.actions.scopeGuard.status=if($guardVerified -eq $scopeGuardIdentities.Count -and $scopeGuardIdentities.Count -gt 0){'VERIFIED'}elseif($scopeGuardIdentities.Count -eq 0){'UNAVAILABLE'}else{'FAILED'}
}
$failureText=(@($rightsResult.Failures) | ForEach-Object {"$($_.accountName):$($_.right):$($_.error)"}) -join ' | '
$mandatoryGuardFailed=($allScope -and -not [bool]$SaveLaps -and ($guardVerified -ne 1))
if($mandatoryGuardFailed){
$script:State.actions.denyRights='FAILED'
$script:State.actions.changesApplied=($rightsResult.AffectedCount -gt 0)
Add-StageResult -Name 'APPLY_DENY_RIGHTS' -Status 'FAILED' -Started $stageStart -AffectedCount $rightsResult.AffectedCount -ErrorCode 'ALL_SCOPE_EVERYONE_GUARD_FAILED' -ErrorMessage $(if($failureText){$failureText}else{'The Everyone interactive/RDP/network deny guard was not verified.'}) -Expected "$($enforcementIdentities.Count) identities including Everyone guard verified" -Actual "$($rightsResult.ProtectedIdentityCount) verified"
}
elseif(@($rightsResult.Failures).Count -gt 0){
$script:State.actions.denyRights='PARTIAL'
$script:State.actions.changesApplied=($rightsResult.AffectedCount -gt 0)
Add-StageResult -Name 'APPLY_DENY_RIGHTS' -Status 'WARNING' -Started $stageStart -AffectedCount $rightsResult.AffectedCount -ErrorCode 'ONE_OR_MORE_DENY_RIGHTS_FAILED' -ErrorMessage $failureText -Expected "$($enforcementIdentities.Count) enforcement identities verified" -Actual "$($rightsResult.ProtectedIdentityCount) verified"
}
else{
$script:State.actions.denyRights='SUCCESS'
$script:State.actions.changesApplied=$true
Add-StageResult -Name 'APPLY_DENY_RIGHTS' -Status 'SUCCESS' -Started $stageStart -AffectedCount $rightsResult.AffectedCount -Expected "$($enforcementIdentities.Count) enforcement identities verified" -Actual "$($rightsResult.ProtectedIdentityCount) verified"
}
}
catch{$script:State.actions.denyRights='FAILED';Add-StageResult -Name 'APPLY_DENY_RIGHTS' -Status 'FAILED' -Started $stageStart -ErrorCode 'DENY_RIGHTS_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow
try{Disable-CachedDomainLogon;$current=[string](Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name CachedLogonsCount -ErrorAction Stop).CachedLogonsCount;$script:State.actions.cachedDomainLogon='DISABLED';$script:State.actions.changesApplied=$true;Add-StageResult -Name 'DISABLE_CACHED_DOMAIN_LOGON' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected '0' -Actual $current}
catch{$script:State.actions.cachedDomainLogon='FAILED';Add-StageResult -Name 'DISABLE_CACHED_DOMAIN_LOGON' -Status 'FAILED' -Started $stageStart -ErrorCode 'CACHED_LOGON_DISABLE_FAILED' -ErrorMessage $_.Exception.Message -Expected '0' -Actual 'UNVERIFIED'}
$domainIsolationRequired=$allScope -and [bool]$script:State.actions.domainContext.partOfDomain
if($domainIsolationRequired){
$stageStart=[DateTime]::UtcNow;try{Disable-Netlogon;$script:State.actions.domainAuthIsolation='PARTIAL';$script:State.actions.changesApplied=$true;$net=Get-NetlogonState;Add-StageResult -Name 'DISABLE_NETLOGON' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected 'Stopped/Disabled' -Actual "$($net.state)/$($net.startMode)"}catch{Add-StageResult -Name 'DISABLE_NETLOGON' -Status 'FAILED' -Started $stageStart -ErrorCode 'NETLOGON_DISABLE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow;try{Disable-KdcProxy;$kdc=Get-KdcProxyState;$kdcPass=($kdc.enabledValueExisted -and [int]$kdc.enabledValue -eq 0 -and -not $kdc.mappingPathExisted);if(-not $kdcPass){throw 'KDC proxy post-change verification failed.'};Add-StageResult -Name 'DISABLE_KDC_PROXY' -Status 'SUCCESS' -Started $stageStart -Expected 'Disabled and mappings absent' -Actual 'Verified'}catch{Add-StageResult -Name 'DISABLE_KDC_PROXY' -Status 'WARNING' -Started $stageStart -ErrorCode 'KDC_PROXY_DISABLE_FAILED' -ErrorMessage $_.Exception.Message}
$stageStart=[DateTime]::UtcNow;try{$created=Apply-AuthFirewallRules -Plan $script:State.actions.authFirewall;$script:State.actions.domainAuthIsolation='SUCCESS';$script:State.actions.changesApplied=$true;Add-StageResult -Name 'APPLY_AUTH_FIREWALL' -Status 'SUCCESS' -Started $stageStart -AffectedCount $created.Count -Expected "$(@($script:State.actions.authFirewall.ruleDefinitions).Count) rules active" -Actual "$($created.Count) rules verified;Mode=$($script:State.actions.authFirewall.mode)"}catch{$script:State.actions.domainAuthIsolation='FAILED';Add-StageResult -Name 'APPLY_AUTH_FIREWALL' -Status 'FAILED' -Started $stageStart -ErrorCode 'AUTH_FIREWALL_FAILED' -ErrorMessage $_.Exception.Message}
}else{Add-StageResult -Name 'DISABLE_NETLOGON' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow);Add-StageResult -Name 'DISABLE_KDC_PROXY' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow);Add-StageResult -Name 'APPLY_AUTH_FIREWALL' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow);$script:State.actions.domainAuthIsolation='NOT_APPLICABLE'}
$scopeGuardCritical=((-not $allScope) -or ([bool]$SaveLaps) -or ([string]$script:State.actions.scopeGuard.status -eq 'VERIFIED'))
$criticalProtectionApplied=$scopeGuardCritical -and ([int]$script:State.actions.protectedIdentityCount -gt 0) -and ((Get-StageStatus 'DISABLE_CACHED_DOMAIN_LOGON') -eq 'SUCCESS') -and ((-not $domainIsolationRequired) -or (((Get-StageStatus 'DISABLE_NETLOGON') -eq 'SUCCESS') -and ((Get-StageStatus 'APPLY_AUTH_FIREWALL') -eq 'SUCCESS')))
if(-not $criticalProtectionApplied){$script:State.status='LOCKDOWN_PARTIAL';$script:State.completedUtc=Get-UtcTimestamp;Add-StageResult -Name 'INSTALL_POSTBOOT_VERIFIER' -Status 'SKIPPED' -Started ([DateTime]::UtcNow);Add-StageResult -Name 'PREPARE_DISRUPTION_CHECKPOINT' -Status 'SKIPPED';Add-StageResult -Name 'INSTALL_DISRUPTION_WORKER' -Status 'SKIPPED';Add-StageResult -Name 'START_DISRUPTION_WORKER' -Status 'SKIPPED' -Started ([DateTime]::UtcNow);Add-StageResult -Name 'DISRUPTION_DELEGATED' -Status 'SKIPPED' -Started ([DateTime]::UtcNow);Add-StageResult -Name 'LOGOFF_SESSIONS' -Status 'SKIPPED' -Started ([DateTime]::UtcNow);Add-StageResult -Name 'SCHEDULE_REBOOT' -Status 'SKIPPED' -Started ([DateTime]::UtcNow) -ErrorCode 'CRITICAL_PROTECTION_INCOMPLETE' -ErrorMessage 'Reboot was not scheduled because one or more required protections failed.';Add-StageResult -Name 'FINALIZE_STATE' -Status 'WARNING' -Started ([DateTime]::UtcNow) -ErrorCode 'LOCKDOWN_PARTIAL' -ErrorMessage 'Review state and restore or remediate before retrying.';Write-Remarks;exit 40}
$stageStart=[DateTime]::UtcNow
try{Install-PostBootVerifier -Operation 'LOCKDOWN';Add-StageResult -Name 'INSTALL_POSTBOOT_VERIFIER' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected 'Startup task query succeeds' -Actual $PostBootTaskName}
catch{Add-StageResult -Name 'INSTALL_POSTBOOT_VERIFIER' -Status 'WARNING' -Started $stageStart -ErrorCode 'POSTBOOT_VERIFIER_INSTALL_FAILED' -ErrorMessage $_.Exception.Message}
$script:State.actions.sessionTelemetry.pendingLogoffSessionIds=@($sessionIds)
$script:State.status='ACTIVE_DISRUPTION_PENDING'
$stageStart=[DateTime]::UtcNow
Add-StageResult -Name 'PREPARE_DISRUPTION_CHECKPOINT' -Status 'SUCCESS' -Started $stageStart -Message 'All rollback and enforcement data were persisted before independent logoff/reboot orchestration.'
$workerReady=$false
$stageStart=[DateTime]::UtcNow
try{Install-DisruptionWorker;Add-StageResult -Name 'INSTALL_DISRUPTION_WORKER' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected 'ACL-protected SYSTEM task registered' -Actual $DisruptionTaskName;$workerReady=$true}
catch{Add-StageResult -Name 'INSTALL_DISRUPTION_WORKER' -Status 'FAILED' -Started $stageStart -ErrorCode 'DISRUPTION_WORKER_INSTALL_FAILED' -ErrorMessage $_.Exception.Message}
if($workerReady){
Add-StageResult -Name 'DISRUPTION_DELEGATED' -Status 'SUCCESS' -Started ([DateTime]::UtcNow) -Expected 'Independent SYSTEM worker owns logoff and reboot actions' -Actual $DisruptionTaskName -Verification 'PASS'
$stageStart=[DateTime]::UtcNow
try{$taskState=Start-DisruptionWorker;Add-StageResult -Name 'START_DISRUPTION_WORKER' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected 'Task started independently from EPC execution session' -Actual $taskState}
catch{Add-StageResult -Name 'START_DISRUPTION_WORKER' -Status 'FAILED' -Started $stageStart -ErrorCode 'DISRUPTION_WORKER_START_FAILED' -ErrorMessage $_.Exception.Message;$workerReady=$false}
}else{
Add-StageResult -Name 'START_DISRUPTION_WORKER' -Status 'SKIPPED' -Started ([DateTime]::UtcNow)
}
if(-not $workerReady){
try{Cancel-DisruptionWorker -Reason 'Worker unavailable; direct reboot fallback used.'}catch{}
$stageStart=[DateTime]::UtcNow
try{Schedule-Reboot;Add-StageResult -Name 'SCHEDULE_REBOOT' -Status 'SUCCESS' -Started $stageStart -AffectedCount 1 -Expected 'shutdown.exe accepted restart request' -Actual "ExitCode=0;Delay=$RebootDelaySeconds seconds" -Verification 'PENDING';Add-StageResult -Name 'LOGOFF_SESSIONS' -Status 'WARNING' -Started ([DateTime]::UtcNow) -ErrorCode 'LOGOFF_DEFERRED_TO_REBOOT' -ErrorMessage 'The independent worker was unavailable. Target sessions will be terminated by the forced reboot.' -Expected 'Immediate target sign-out' -Actual 'Deferred to reboot'}
catch{try{Cancel-PostBootVerifier -Reason 'Disruption worker and direct reboot fallback both failed.'}catch{};Add-StageResult -Name 'SCHEDULE_REBOOT' -Status 'FAILED' -Started $stageStart -ErrorCode 'REBOOT_SCHEDULE_FAILED' -ErrorMessage $_.Exception.Message;Add-StageResult -Name 'LOGOFF_SESSIONS' -Status 'SKIPPED' -Started ([DateTime]::UtcNow)}
}
$stageStart=[DateTime]::UtcNow
$script:State.completedUtc=Get-UtcTimestamp
$script:State.status=if($workerReady){'ACTIVE_DISRUPTION_PENDING'}elseif((Get-StageStatus 'SCHEDULE_REBOOT') -eq 'SUCCESS'){'ACTIVE_PENDING_REBOOT'}else{'LOCKDOWN_PARTIAL'}
Add-StageResult -Name 'FINALIZE_STATE' -Status $(if($script:State.status -in @('ACTIVE_DISRUPTION_PENDING','ACTIVE_PENDING_REBOOT')){'SUCCESS'}else{'WARNING'}) -Started $stageStart -ErrorCode $(if($script:State.status -in @('ACTIVE_DISRUPTION_PENDING','ACTIVE_PENDING_REBOOT')){$null}else{'LOCKDOWN_PARTIAL'}) -ErrorMessage $(if($script:State.status -in @('ACTIVE_DISRUPTION_PENDING','ACTIVE_PENDING_REBOOT')){$null}else{'Lockdown completed with warnings or failures. Review the state file.'})
Write-Remarks;exit $(if($script:State.status -in @('ACTIVE_DISRUPTION_PENDING','ACTIVE_PENDING_REBOOT')){0}else{2})
}
catch{
try{$script:State.status='UNHANDLED_FAILURE';$script:State.completedUtc=Get-UtcTimestamp;Add-ErrorRecord -Code 'UNHANDLED_EXCEPTION' -Message $_.Exception.Message -Stage 'UNHANDLED';if($script:StateWriteEnabled){Write-StateCheckpoint};Write-Remarks}catch{[Console]::Out.WriteLine((([ordered]@{script='EmergencyLockdown';version=$ScriptVersion;operation='LOCKDOWN';result='FAILED';failed='UNHANDLED_EXCEPTION;REMARKS_GENERATION_FAILED';message=$_.Exception.Message}) | ConvertTo-Json -Compress))}
exit 99
}Kilitlemeyi kaldırma betiği
<#
.SYNOPSIS
Techaptic - Endpoint Central Emergency Restore Script (V1.4.0)
.DESCRIPTION
This Techaptic script restores only the access and security changes recorded by the matching
Emergency Lockdown state file. It is designed to run as SYSTEM through ManageEngine Endpoint
Central and accepts no operator parameters.
Restore removes only the recorded Lockdown deltas. It does not recreate user sessions, restart
previously running applications, or start unrelated services and scheduled tasks. A temporary
post-boot verifier script may appear in the protected state directory after restart and removes
itself after the final verification is written to the JSON state file.
.EXAMPLE
.\Techaptic-EPC-EmergencyRestore-v1.4.0.ps1
.NOTES
Author : Techaptic
Maintainer : Techaptic
Script : Endpoint Central Emergency Restore
Version : 1.4.0
Execution : Endpoint Central Computer Configuration / Run as SYSTEM
State File : C:\ProgramData\EmergencyLockdown\EmergencyLockdown.json
IMPORTANT SAFETY NOTES:
- Use this script only with a valid state file created by the corresponding Techaptic Lockdown
script.
- Do not delete, rename, replace, or manually edit EmergencyLockdown.json before Restore.
- Validate the complete Lockdown and Restore cycle in a representative test environment before
any production use.
- Restore may initiate a restart. Endpoint Central Remarks is the immediate execution result;
the final reboot verification is written to the protected JSON state file.
- A temporary EmergencyPostBootVerify.ps1 file after restart is expected and normally deletes
itself after verification completes.
- Do not modify embedded Base64 components, internal task names, state paths, or rollback logic
unless the complete test matrix is repeated.
- Customer change approval, backup, rollback, access-control, and production validation
procedures remain mandatory.
#>
[CmdletBinding()]
param()
Set-StrictMode -Version 2.0
$ErrorActionPreference='Stop'
$ProgressPreference='SilentlyContinue'
$ScriptVersion='1.4.0'
$BaseDirectory=Join-Path $env:ProgramData 'EmergencyLockdown'
$StatePath=Join-Path $BaseDirectory 'EmergencyLockdown.json'
$TemporaryStatePath=Join-Path $BaseDirectory 'EmergencyLockdown.tmp'
$RebootDelaySeconds=60
$PostBootVerifierPath=Join-Path $BaseDirectory 'EmergencyPostBootVerify.ps1'
$PostBootTaskName='EmergencyLockdown-PostBootVerify-v140'
$DisruptionWorkerPath=Join-Path $BaseDirectory 'EmergencyDisruptionWorker.ps1'
$DisruptionTaskNames=@('EmergencyLockdown-DisruptionWorker-v140','EmergencyLockdown-DisruptionWorker-v135','EmergencyLockdown-DisruptionWorker-v134','EmergencyLockdown-DisruptionWorker-v133','EmergencyLockdown-DisruptionWorker-v132','EmergencyLockdown-DisruptionWorker-v131')
$PostBootVerifierBase64 = 'PCMKLlNZTk9QU0lTCiAgICBBdXRvbWF0aWMgcG9zdC1ib290IHZlcmlmaWNhdGlvbiBjb21wb25lbnQgZm9yIEVtZXJnZW5jeSBMb2NrZG93biBWMS40LjAuCi5ERVNDUklQVElPTgogICAgR2VuZXJhdGVkIGFuZCByZWdpc3RlcmVkIGludGVybmFsbHkgYnkgdGhlIExvY2tkb3duL1Jlc3RvcmUgc2NyaXB0cy4KICAgIEl0IHJ1bnMgYXMgU1lTVEVNIGFmdGVyIHJlYm9vdCwgdmVyaWZpZXMgZWZmZWN0aXZlIHN0YXRlLCB1cGRhdGVzIHRoZSBBQ0wtcHJvdGVjdGVkIEpTT04sIGFuZCByZW1vdmVzIGl0cyBvd24gdGFzay4KICAgIEl0IGlzIG5vdCBhbiBvcGVyYXRvci1mYWNpbmcgRW5kcG9pbnQgQ2VudHJhbCBzY3JpcHQuCiM+CnBhcmFtKCkKU2V0LVN0cmljdE1vZGUgLVZlcnNpb24gMi4wCiRFcnJvckFjdGlvblByZWZlcmVuY2UgPSAnU3RvcCcKJFByb2dyZXNzUHJlZmVyZW5jZSA9ICdTaWxlbnRseUNvbnRpbnVlJwoKJEJhc2VEaXJlY3RvcnkgPSBKb2luLVBhdGggJGVudjpQcm9ncmFtRGF0YSAnRW1lcmdlbmN5TG9ja2Rvd24nCiRTdGF0ZVBhdGggPSBKb2luLVBhdGggJEJhc2VEaXJlY3RvcnkgJ0VtZXJnZW5jeUxvY2tkb3duLmpzb24nCiRUZW1wb3JhcnlTdGF0ZVBhdGggPSBKb2luLVBhdGggJEJhc2VEaXJlY3RvcnkgJ0VtZXJnZW5jeUxvY2tkb3duLnRtcCcKJFRhc2tOYW1lID0gJ0VtZXJnZW5jeUxvY2tkb3duLVBvc3RCb290VmVyaWZ5LXYxNDAnCiRzY3JpcHQ6VmVyaWZpY2F0aW9uUmVjb3JkZWQgPSAkZmFsc2UKU3RhcnQtU2xlZXAgLVNlY29uZHMgNjAKCmZ1bmN0aW9uIEdldC1VdGNUaW1lc3RhbXAgeyBbRGF0ZVRpbWVdOjpVdGNOb3cuVG9TdHJpbmcoJ28nKSB9CmZ1bmN0aW9uIEdldC1Qcm9wZXJ0eVZhbHVlIHsKICAgIHBhcmFtKCRPYmplY3QsW3N0cmluZ10kTmFtZSwkRGVmYXVsdD0kbnVsbCkKICAgIGlmICgkbnVsbCAtZXEgJE9iamVjdCkgeyByZXR1cm4gJERlZmF1bHQgfQogICAgJHByb3BlcnR5ID0gJE9iamVjdC5QU09iamVjdC5Qcm9wZXJ0aWVzWyROYW1lXQogICAgaWYgKCRudWxsIC1lcSAkcHJvcGVydHkpIHsgcmV0dXJuICREZWZhdWx0IH0KICAgIHJldHVybiAkcHJvcGVydHkuVmFsdWUKfQoKZnVuY3Rpb24gSW5pdGlhbGl6ZS1XdHNOYXRpdmVUeXBlIHsKICAgIGlmICgnRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3RXdHNWMTQwJyAtYXMgW3R5cGVdKSB7IHJldHVybiB9CiAgICAkc291cmNlID0gQCcKdXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkNvbXBvbmVudE1vZGVsOwp1c2luZyBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXM7Cm5hbWVzcGFjZSBFbWVyZ2VuY3lTZWN1cml0eQp7CiAgICBwdWJsaWMgc3RhdGljIGNsYXNzIFBvc3RCb290V3RzVjE0MAogICAgewogICAgICAgIFtTdHJ1Y3RMYXlvdXQoTGF5b3V0S2luZC5TZXF1ZW50aWFsLCBDaGFyU2V0PUNoYXJTZXQuVW5pY29kZSldCiAgICAgICAgcHJpdmF0ZSBzdHJ1Y3QgV1RTX1NFU1NJT05fSU5GTyB7IHB1YmxpYyBpbnQgU2Vzc2lvbklEOyBwdWJsaWMgc3RyaW5nIHBXaW5TdGF0aW9uTmFtZTsgcHVibGljIGludCBTdGF0ZTsgfQogICAgICAgIHByaXZhdGUgZW51bSBXVFNfSU5GT19DTEFTUyB7IFdUU0luaXRpYWxQcm9ncmFtLCBXVFNBcHBsaWNhdGlvbk5hbWUsIFdUU1dvcmtpbmdEaXJlY3RvcnksIFdUU09FTUlkLCBXVFNTZXNzaW9uSWQsIFdUU1VzZXJOYW1lLCBXVFNXaW5TdGF0aW9uTmFtZSwgV1RTRG9tYWluTmFtZSB9CiAgICAgICAgW0RsbEltcG9ydCgid3RzYXBpMzIuZGxsIiwgU2V0TGFzdEVycm9yPXRydWUpXSBwcml2YXRlIHN0YXRpYyBleHRlcm4gYm9vbCBXVFNFbnVtZXJhdGVTZXNzaW9ucyhJbnRQdHIgaFNlcnZlciwgaW50IFJlc2VydmVkLCBpbnQgVmVyc2lvbiwgb3V0IEludFB0ciBwcFNlc3Npb25JbmZvLCBvdXQgaW50IHBDb3VudCk7CiAgICAgICAgW0RsbEltcG9ydCgid3RzYXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB2b2lkIFdUU0ZyZWVNZW1vcnkoSW50UHRyIHBNZW1vcnkpOwogICAgICAgIFtEbGxJbXBvcnQoInd0c2FwaTMyLmRsbCIsIENoYXJTZXQ9Q2hhclNldC5Vbmljb2RlLCBTZXRMYXN0RXJyb3I9dHJ1ZSldIHByaXZhdGUgc3RhdGljIGV4dGVybiBib29sIFdUU1F1ZXJ5U2Vzc2lvbkluZm9ybWF0aW9uKEludFB0ciBoU2VydmVyLCBpbnQgc2Vzc2lvbklkLCBXVFNfSU5GT19DTEFTUyBpbmZvQ2xhc3MsIG91dCBJbnRQdHIgcHBCdWZmZXIsIG91dCBpbnQgcEJ5dGVzUmV0dXJuZWQpOwogICAgICAgIHByaXZhdGUgc3RhdGljIHN0cmluZyBRdWVyeShpbnQgc2Vzc2lvbklkLCBXVFNfSU5GT19DTEFTUyBpbmZvQ2xhc3MpIHsgSW50UHRyIHA9SW50UHRyLlplcm87IGludCBieXRlcz0wOyB0cnkgeyBpZighV1RTUXVlcnlTZXNzaW9uSW5mb3JtYXRpb24oSW50UHRyLlplcm8sc2Vzc2lvbklkLGluZm9DbGFzcyxvdXQgcCxvdXQgYnl0ZXMpKSByZXR1cm4gIiI7IHJldHVybiBwPT1JbnRQdHIuWmVybyA/ICIiIDogTWFyc2hhbC5QdHJUb1N0cmluZ1VuaShwKTsgfSBmaW5hbGx5IHsgaWYocCE9SW50UHRyLlplcm8pIFdUU0ZyZWVNZW1vcnkocCk7IH0gfQogICAgICAgIHB1YmxpYyBzdGF0aWMgc3RyaW5nW10gR2V0U2Vzc2lvbnMoKSB7IEludFB0ciBidWZmZXI9SW50UHRyLlplcm87IGludCBjb3VudD0wOyBMaXN0PHN0cmluZz4gcmVzdWx0PW5ldyBMaXN0PHN0cmluZz4oKTsgdHJ5IHsgaWYoIVdUU0VudW1lcmF0ZVNlc3Npb25zKEludFB0ci5aZXJvLDAsMSxvdXQgYnVmZmVyLG91dCBjb3VudCkpIHRocm93IG5ldyBXaW4zMkV4Y2VwdGlvbihNYXJzaGFsLkdldExhc3RXaW4zMkVycm9yKCkpOyBpbnQgc2l6ZT1NYXJzaGFsLlNpemVPZih0eXBlb2YoV1RTX1NFU1NJT05fSU5GTykpOyBmb3IoaW50IGk9MDtpPGNvdW50O2krKyl7IFdUU19TRVNTSU9OX0lORk8gaXRlbT0oV1RTX1NFU1NJT05fSU5GTylNYXJzaGFsLlB0clRvU3RydWN0dXJlKEludFB0ci5BZGQoYnVmZmVyLGkqc2l6ZSksdHlwZW9mKFdUU19TRVNTSU9OX0lORk8pKTsgc3RyaW5nIHVzZXI9UXVlcnkoaXRlbS5TZXNzaW9uSUQsV1RTX0lORk9fQ0xBU1MuV1RTVXNlck5hbWUpOyBzdHJpbmcgZG9tYWluPVF1ZXJ5KGl0ZW0uU2Vzc2lvbklELFdUU19JTkZPX0NMQVNTLldUU0RvbWFpbk5hbWUpOyByZXN1bHQuQWRkKGl0ZW0uU2Vzc2lvbklEKyJ8Iitkb21haW4rInwiK3VzZXIrInwiK2l0ZW0uU3RhdGUpOyB9IHJldHVybiByZXN1bHQuVG9BcnJheSgpOyB9IGZpbmFsbHkgeyBpZihidWZmZXIhPUludFB0ci5aZXJvKSBXVFNGcmVlTWVtb3J5KGJ1ZmZlcik7IH0gfQogICAgfQp9CidACiAgICBBZGQtVHlwZSAtVHlwZURlZmluaXRpb24gJHNvdXJjZSAtTGFuZ3VhZ2UgQ1NoYXJwIC1FcnJvckFjdGlvbiBTdG9wIHwgT3V0LU51bGwKfQpmdW5jdGlvbiBHZXQtV3RzU2Vzc2lvbnMgewogICAgJGl0ZW1zID0gQCgpCiAgICBmb3JlYWNoICgkbGluZSBpbiBbRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3RXdHNWMTQwXTo6R2V0U2Vzc2lvbnMoKSkgewogICAgICAgICRwYXJ0cyA9ICRsaW5lLlNwbGl0KCd8JykKICAgICAgICBpZiAoJHBhcnRzLkNvdW50IC1sdCA0KSB7IGNvbnRpbnVlIH0KICAgICAgICAkaWQgPSBbaW50XSRwYXJ0c1swXQogICAgICAgICRkb21haW4gPSBbc3RyaW5nXSRwYXJ0c1sxXQogICAgICAgICR1c2VyID0gW3N0cmluZ10kcGFydHNbMl0KICAgICAgICBpZiAoJGlkIC1sZSAwIC1vciBbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCR1c2VyKSkgeyBjb250aW51ZSB9CiAgICAgICAgJGFjY291bnQgPSBpZiAoW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkZG9tYWluKSkgeyAkdXNlciB9IGVsc2UgeyAiJGRvbWFpblwkdXNlciIgfQogICAgICAgICRpdGVtcyArPSBbcHNjdXN0b21vYmplY3RdQHtzZXNzaW9uSWQ9JGlkO2FjY291bnROYW1lPSRhY2NvdW50O3N0YXRlPVtpbnRdJHBhcnRzWzNdfQogICAgfQogICAgcmV0dXJuICRpdGVtcwp9CmZ1bmN0aW9uIE5vcm1hbGl6ZS1TZXNzaW9uQWNjb3VudE5hbWUgewogICAgcGFyYW0oW3N0cmluZ10kTmFtZSkKICAgIGlmIChbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCROYW1lKSkgeyByZXR1cm4gJycgfQogICAgJHZhbHVlPSROYW1lLlRyaW0oKQogICAgaWYgKCR2YWx1ZS5TdGFydHNXaXRoKCcuXCcpKSB7IHJldHVybiAiJGVudjpDT01QVVRFUk5BTUVcJCgkdmFsdWUuU3Vic3RyaW5nKDIpKSIuVG9Mb3dlckludmFyaWFudCgpIH0KICAgIHJldHVybiAkdmFsdWUuVG9Mb3dlckludmFyaWFudCgpCn0KZnVuY3Rpb24gU2V0LVNlY3VyZURpcmVjdG9yeUFjbCB7CiAgICBwYXJhbShbc3RyaW5nXSRQYXRoKQogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFBhdGgpKSB7ICRudWxsID0gTmV3LUl0ZW0gLVBhdGggJFBhdGggLUl0ZW1UeXBlIERpcmVjdG9yeSAtRm9yY2UgfQogICAgJHN5c3RlbVNpZCA9IFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLlNlY3VyaXR5SWRlbnRpZmllcl06Om5ldygnUy0xLTUtMTgnKQogICAgJGFkbWluaXN0cmF0b3JzU2lkID0gW1N5c3RlbS5TZWN1cml0eS5QcmluY2lwYWwuU2VjdXJpdHlJZGVudGlmaWVyXTo6bmV3KCdTLTEtNS0zMi01NDQnKQogICAgJGluaGVyaXRhbmNlID0gW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkluaGVyaXRhbmNlRmxhZ3NdOjpDb250YWluZXJJbmhlcml0IC1ib3IgW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkluaGVyaXRhbmNlRmxhZ3NdOjpPYmplY3RJbmhlcml0CiAgICAkc2VjdXJpdHkgPSBbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRGlyZWN0b3J5U2VjdXJpdHldOjpuZXcoKQogICAgJHNlY3VyaXR5LlNldEFjY2Vzc1J1bGVQcm90ZWN0aW9uKCR0cnVlLCRmYWxzZSkKICAgICRzZWN1cml0eS5TZXRPd25lcigkc3lzdGVtU2lkKQogICAgJHNlY3VyaXR5LkFkZEFjY2Vzc1J1bGUoW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1BY2Nlc3NSdWxlXTo6bmV3KCRzeXN0ZW1TaWQsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1SaWdodHNdOjpGdWxsQ29udHJvbCwkaW5oZXJpdGFuY2UsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLlByb3BhZ2F0aW9uRmxhZ3NdOjpOb25lLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgICRzZWN1cml0eS5BZGRBY2Nlc3NSdWxlKFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5GaWxlU3lzdGVtQWNjZXNzUnVsZV06Om5ldygkYWRtaW5pc3RyYXRvcnNTaWQsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1SaWdodHNdOjpGdWxsQ29udHJvbCwkaW5oZXJpdGFuY2UsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLlByb3BhZ2F0aW9uRmxhZ3NdOjpOb25lLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgIFtTeXN0ZW0uSU8uRGlyZWN0b3J5SW5mb106Om5ldygkUGF0aCkuU2V0QWNjZXNzQ29udHJvbCgkc2VjdXJpdHkpCn0KZnVuY3Rpb24gU2V0LVNlY3VyZUZpbGVBY2wgewogICAgcGFyYW0oW3N0cmluZ10kUGF0aCkKICAgIGlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRQYXRoKSkgeyByZXR1cm4gfQogICAgJHN5c3RlbVNpZCA9IFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLlNlY3VyaXR5SWRlbnRpZmllcl06Om5ldygnUy0xLTUtMTgnKQogICAgJGFkbWluaXN0cmF0b3JzU2lkID0gW1N5c3RlbS5TZWN1cml0eS5QcmluY2lwYWwuU2VjdXJpdHlJZGVudGlmaWVyXTo6bmV3KCdTLTEtNS0zMi01NDQnKQogICAgJHNlY3VyaXR5ID0gW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTZWN1cml0eV06Om5ldygpCiAgICAkc2VjdXJpdHkuU2V0QWNjZXNzUnVsZVByb3RlY3Rpb24oJHRydWUsJGZhbHNlKQogICAgJHNlY3VyaXR5LlNldE93bmVyKCRzeXN0ZW1TaWQpCiAgICAkc2VjdXJpdHkuQWRkQWNjZXNzUnVsZShbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbUFjY2Vzc1J1bGVdOjpuZXcoJHN5c3RlbVNpZCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuRmlsZVN5c3RlbVJpZ2h0c106OkZ1bGxDb250cm9sLFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5BY2Nlc3NDb250cm9sVHlwZV06OkFsbG93KSkKICAgICRzZWN1cml0eS5BZGRBY2Nlc3NSdWxlKFtTeXN0ZW0uU2VjdXJpdHkuQWNjZXNzQ29udHJvbC5GaWxlU3lzdGVtQWNjZXNzUnVsZV06Om5ldygkYWRtaW5pc3RyYXRvcnNTaWQsW1N5c3RlbS5TZWN1cml0eS5BY2Nlc3NDb250cm9sLkZpbGVTeXN0ZW1SaWdodHNdOjpGdWxsQ29udHJvbCxbU3lzdGVtLlNlY3VyaXR5LkFjY2Vzc0NvbnRyb2wuQWNjZXNzQ29udHJvbFR5cGVdOjpBbGxvdykpCiAgICBbU3lzdGVtLklPLkZpbGVJbmZvXTo6bmV3KCRQYXRoKS5TZXRBY2Nlc3NDb250cm9sKCRzZWN1cml0eSkKfQpmdW5jdGlvbiBXcml0ZS1TdGF0ZSB7CiAgICBwYXJhbSgkU3RhdGUpCiAgICBTZXQtU2VjdXJlRGlyZWN0b3J5QWNsIC1QYXRoICRCYXNlRGlyZWN0b3J5CiAgICAkanNvbiA9ICRTdGF0ZSB8IENvbnZlcnRUby1Kc29uIC1EZXB0aCAyNAogICAgW1N5c3RlbS5JTy5GaWxlXTo6V3JpdGVBbGxUZXh0KCRUZW1wb3JhcnlTdGF0ZVBhdGgsJGpzb24sW1N5c3RlbS5UZXh0LlVURjhFbmNvZGluZ106Om5ldygkZmFsc2UpKQogICAgU2V0LVNlY3VyZUZpbGVBY2wgLVBhdGggJFRlbXBvcmFyeVN0YXRlUGF0aAogICAgTW92ZS1JdGVtIC1MaXRlcmFsUGF0aCAkVGVtcG9yYXJ5U3RhdGVQYXRoIC1EZXN0aW5hdGlvbiAkU3RhdGVQYXRoIC1Gb3JjZQogICAgU2V0LVNlY3VyZUZpbGVBY2wgLVBhdGggJFN0YXRlUGF0aAp9CmZ1bmN0aW9uIE5ldy1DaGVjayB7CiAgICBwYXJhbShbc3RyaW5nXSROYW1lLFtWYWxpZGF0ZVNldCgnUEFTUycsJ0ZBSUwnLCdXQVJOSU5HJywnTk9UX0FQUExJQ0FCTEUnKV1bc3RyaW5nXSRTdGF0dXMsW3N0cmluZ10kRXhwZWN0ZWQsW3N0cmluZ10kQWN0dWFsLFtzdHJpbmddJE1lc3NhZ2U9JG51bGwpCiAgICBbcHNjdXN0b21vYmplY3RdW29yZGVyZWRdQHtuYW1lPSROYW1lO3N0YXR1cz0kU3RhdHVzO2V4cGVjdGVkPSRFeHBlY3RlZDthY3R1YWw9JEFjdHVhbDttZXNzYWdlPSRNZXNzYWdlfQp9CmZ1bmN0aW9uIFRlc3QtU2VjdXJlQWNsIHsKICAgIHBhcmFtKFtzdHJpbmddJFBhdGgpCiAgICB0cnkgewogICAgICAgICRhY2wgPSBHZXQtQWNsIC1MaXRlcmFsUGF0aCAkUGF0aCAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgICRvd25lclNpZCA9IHRyeSB7IChbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5OVEFjY291bnRdJGFjbC5Pd25lcikuVHJhbnNsYXRlKFtTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLlNlY3VyaXR5SWRlbnRpZmllcl0pLlZhbHVlIH0gY2F0Y2ggeyBbc3RyaW5nXSRhY2wuT3duZXIgfQogICAgICAgICRhbGxvd2VkID0gQCgnUy0xLTUtMTgnLCdTLTEtNS0zMi01NDQnKQogICAgICAgICR1bmV4cGVjdGVkID0gQCgpCiAgICAgICAgZm9yZWFjaCAoJHJ1bGUgaW4gQCgkYWNsLkFjY2VzcykpIHsKICAgICAgICAgICAgJHNpZCA9IHRyeSB7ICRydWxlLklkZW50aXR5UmVmZXJlbmNlLlRyYW5zbGF0ZShbU3lzdGVtLlNlY3VyaXR5LlByaW5jaXBhbC5TZWN1cml0eUlkZW50aWZpZXJdKS5WYWx1ZSB9IGNhdGNoIHsgW3N0cmluZ10kcnVsZS5JZGVudGl0eVJlZmVyZW5jZSB9CiAgICAgICAgICAgIGlmICgkcnVsZS5BY2Nlc3NDb250cm9sVHlwZSAtbmUgJ0FsbG93JyAtb3IgJHNpZCAtbm90aW4gJGFsbG93ZWQpIHsgJHVuZXhwZWN0ZWQgKz0gJHNpZCB9CiAgICAgICAgfQogICAgICAgIHJldHVybiBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSgkYWNsLkFyZUFjY2Vzc1J1bGVzUHJvdGVjdGVkIC1hbmQgJG93bmVyU2lkIC1lcSAnUy0xLTUtMTgnIC1hbmQgJHVuZXhwZWN0ZWQuQ291bnQgLWVxIDApO093bmVyPSRvd25lclNpZDtQcm90ZWN0ZWQ9W2Jvb2xdJGFjbC5BcmVBY2Nlc3NSdWxlc1Byb3RlY3RlZDtVbmV4cGVjdGVkPSgkdW5leHBlY3RlZCAtam9pbiAnOycpfQogICAgfSBjYXRjaCB7IHJldHVybiBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSRmYWxzZTtPd25lcj0nVU5LTk9XTic7UHJvdGVjdGVkPSRmYWxzZTtVbmV4cGVjdGVkPSRfLkV4Y2VwdGlvbi5NZXNzYWdlfSB9Cn0KZnVuY3Rpb24gSW5pdGlhbGl6ZS1Mc2FOYXRpdmVUeXBlIHsKICAgIGlmICgnRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3ROYXRpdmVWMTQwJyAtYXMgW3R5cGVdKSB7IHJldHVybiB9CiAgICAkc291cmNlID0gQCcKdXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkNvbXBvbmVudE1vZGVsOwp1c2luZyBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXM7CnVzaW5nIFN5c3RlbS5TZWN1cml0eS5QcmluY2lwYWw7Cm5hbWVzcGFjZSBFbWVyZ2VuY3lTZWN1cml0eQp7CiAgICBwdWJsaWMgc3RhdGljIGNsYXNzIFBvc3RCb290TmF0aXZlVjE0MAogICAgewogICAgICAgIFtTdHJ1Y3RMYXlvdXQoTGF5b3V0S2luZC5TZXF1ZW50aWFsKV0gcHJpdmF0ZSBzdHJ1Y3QgTFNBX09CSkVDVF9BVFRSSUJVVEVTIHsgcHVibGljIGludCBMZW5ndGg7IHB1YmxpYyBJbnRQdHIgUm9vdERpcmVjdG9yeTsgcHVibGljIEludFB0ciBPYmplY3ROYW1lOyBwdWJsaWMgdWludCBBdHRyaWJ1dGVzOyBwdWJsaWMgSW50UHRyIFNlY3VyaXR5RGVzY3JpcHRvcjsgcHVibGljIEludFB0ciBTZWN1cml0eVF1YWxpdHlPZlNlcnZpY2U7IH0KICAgICAgICBbU3RydWN0TGF5b3V0KExheW91dEtpbmQuU2VxdWVudGlhbCldIHByaXZhdGUgc3RydWN0IExTQV9VTklDT0RFX1NUUklORyB7IHB1YmxpYyB1c2hvcnQgTGVuZ3RoOyBwdWJsaWMgdXNob3J0IE1heGltdW1MZW5ndGg7IHB1YmxpYyBJbnRQdHIgQnVmZmVyOyB9CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYU9wZW5Qb2xpY3koSW50UHRyIFN5c3RlbU5hbWUsIHJlZiBMU0FfT0JKRUNUX0FUVFJJQlVURVMgT2JqZWN0QXR0cmlidXRlcywgdWludCBEZXNpcmVkQWNjZXNzLCBvdXQgSW50UHRyIFBvbGljeUhhbmRsZSk7CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYUVudW1lcmF0ZUFjY291bnRSaWdodHMoSW50UHRyIFBvbGljeUhhbmRsZSwgSW50UHRyIEFjY291bnRTaWQsIG91dCBJbnRQdHIgVXNlclJpZ2h0cywgb3V0IHVpbnQgQ291bnRPZlJpZ2h0cyk7CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYU50U3RhdHVzVG9XaW5FcnJvcih1aW50IFN0YXR1cyk7CiAgICAgICAgW0RsbEltcG9ydCgiYWR2YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiB1aW50IExzYUNsb3NlKEludFB0ciBQb2xpY3lIYW5kbGUpOwogICAgICAgIFtEbGxJbXBvcnQoImFkdmFwaTMyLmRsbCIpXSBwcml2YXRlIHN0YXRpYyBleHRlcm4gdWludCBMc2FGcmVlTWVtb3J5KEludFB0ciBCdWZmZXIpOwogICAgICAgIFtEbGxJbXBvcnQoIk5ldGFwaTMyLmRsbCIsIENoYXJTZXQ9Q2hhclNldC5Vbmljb2RlKV0gcHJpdmF0ZSBzdGF0aWMgZXh0ZXJuIGludCBOZXRMb2NhbEdyb3VwR2V0TWVtYmVycyhzdHJpbmcgc2VydmVybmFtZSwgc3RyaW5nIGxvY2FsZ3JvdXBuYW1lLCBpbnQgbGV2ZWwsIG91dCBJbnRQdHIgYnVmcHRyLCBpbnQgcHJlZm1heGxlbiwgb3V0IGludCBlbnRyaWVzcmVhZCwgb3V0IGludCB0b3RhbGVudHJpZXMsIHJlZiBJbnRQdHIgcmVzdW1lX2hhbmRsZSk7CiAgICAgICAgW0RsbEltcG9ydCgiTmV0YXBpMzIuZGxsIildIHByaXZhdGUgc3RhdGljIGV4dGVybiBpbnQgTmV0QXBpQnVmZmVyRnJlZShJbnRQdHIgQnVmZmVyKTsKICAgICAgICBwcml2YXRlIGNvbnN0IHVpbnQgUE9MSUNZX0xPT0tVUF9OQU1FUz0weDAwMDAwODAwOwogICAgICAgIHByaXZhdGUgY29uc3QgdWludCBTVEFUVVNfT0JKRUNUX05BTUVfTk9UX0ZPVU5EPTB4QzAwMDAwMzQ7CiAgICAgICAgcHJpdmF0ZSBjb25zdCBpbnQgTUFYX1BSRUZFUlJFRF9MRU5HVEg9LTE7CiAgICAgICAgcHJpdmF0ZSBjb25zdCBpbnQgRVJST1JfTU9SRV9EQVRBPTIzNDsKICAgICAgICBwcml2YXRlIHN0YXRpYyBJbnRQdHIgU2lkVG9QdHIoc3RyaW5nIHNpZFZhbHVlKXsgU2VjdXJpdHlJZGVudGlmaWVyIHNpZD1uZXcgU2VjdXJpdHlJZGVudGlmaWVyKHNpZFZhbHVlKTsgYnl0ZVtdIGI9bmV3IGJ5dGVbc2lkLkJpbmFyeUxlbmd0aF07IHNpZC5HZXRCaW5hcnlGb3JtKGIsMCk7IEludFB0ciBwPU1hcnNoYWwuQWxsb2NIR2xvYmFsKGIuTGVuZ3RoKTsgTWFyc2hhbC5Db3B5KGIsMCxwLGIuTGVuZ3RoKTsgcmV0dXJuIHA7IH0KICAgICAgICBwdWJsaWMgc3RhdGljIHN0cmluZ1tdIEdldFJpZ2h0cyhzdHJpbmcgc2lkVmFsdWUpCiAgICAgICAgewogICAgICAgICAgICBMU0FfT0JKRUNUX0FUVFJJQlVURVMgbG9hPW5ldyBMU0FfT0JKRUNUX0FUVFJJQlVURVMoKTsgbG9hLkxlbmd0aD1NYXJzaGFsLlNpemVPZih0eXBlb2YoTFNBX09CSkVDVF9BVFRSSUJVVEVTKSk7CiAgICAgICAgICAgIEludFB0ciBwb2xpY3k9SW50UHRyLlplcm87IEludFB0ciBzaWQ9SW50UHRyLlplcm87IEludFB0ciByaWdodHM9SW50UHRyLlplcm87CiAgICAgICAgICAgIHRyeQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICB1aW50IHN0YXR1cz1Mc2FPcGVuUG9saWN5KEludFB0ci5aZXJvLHJlZiBsb2EsUE9MSUNZX0xPT0tVUF9OQU1FUyxvdXQgcG9saWN5KTsKICAgICAgICAgICAgICAgIGlmKHN0YXR1cyE9MCkgdGhyb3cgbmV3IFdpbjMyRXhjZXB0aW9uKChpbnQpTHNhTnRTdGF0dXNUb1dpbkVycm9yKHN0YXR1cykpOwogICAgICAgICAgICAgICAgc2lkPVNpZFRvUHRyKHNpZFZhbHVlKTsgdWludCBjb3VudDsKICAgICAgICAgICAgICAgIHN0YXR1cz1Mc2FFbnVtZXJhdGVBY2NvdW50UmlnaHRzKHBvbGljeSxzaWQsb3V0IHJpZ2h0cyxvdXQgY291bnQpOwogICAgICAgICAgICAgICAgaWYoc3RhdHVzPT1TVEFUVVNfT0JKRUNUX05BTUVfTk9UX0ZPVU5EKSByZXR1cm4gbmV3IHN0cmluZ1swXTsKICAgICAgICAgICAgICAgIGlmKHN0YXR1cyE9MCkgdGhyb3cgbmV3IFdpbjMyRXhjZXB0aW9uKChpbnQpTHNhTnRTdGF0dXNUb1dpbkVycm9yKHN0YXR1cykpOwogICAgICAgICAgICAgICAgTGlzdDxzdHJpbmc+IGxpc3Q9bmV3IExpc3Q8c3RyaW5nPigpOyBpbnQgc2l6ZT1NYXJzaGFsLlNpemVPZih0eXBlb2YoTFNBX1VOSUNPREVfU1RSSU5HKSk7CiAgICAgICAgICAgICAgICBmb3IoaW50IGk9MDtpPGNvdW50O2krKykgeyBMU0FfVU5JQ09ERV9TVFJJTkcgaXRlbT0oTFNBX1VOSUNPREVfU1RSSU5HKU1hcnNoYWwuUHRyVG9TdHJ1Y3R1cmUoSW50UHRyLkFkZChyaWdodHMsaSpzaXplKSx0eXBlb2YoTFNBX1VOSUNPREVfU1RSSU5HKSk7IGxpc3QuQWRkKE1hcnNoYWwuUHRyVG9TdHJpbmdVbmkoaXRlbS5CdWZmZXIsaXRlbS5MZW5ndGgvMikpOyB9CiAgICAgICAgICAgICAgICByZXR1cm4gbGlzdC5Ub0FycmF5KCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZmluYWxseSB7IGlmKHJpZ2h0cyE9SW50UHRyLlplcm8pTHNhRnJlZU1lbW9yeShyaWdodHMpOyBpZihzaWQhPUludFB0ci5aZXJvKU1hcnNoYWwuRnJlZUhHbG9iYWwoc2lkKTsgaWYocG9saWN5IT1JbnRQdHIuWmVybylMc2FDbG9zZShwb2xpY3kpOyB9CiAgICAgICAgfQoKICAgICAgICBwdWJsaWMgc3RhdGljIHN0cmluZ1tdIEdldExvY2FsR3JvdXBNZW1iZXJTaWRzKHN0cmluZyBncm91cE5hbWUpCiAgICAgICAgewogICAgICAgICAgICBMaXN0PHN0cmluZz4gcmVzdWx0cz1uZXcgTGlzdDxzdHJpbmc+KCk7CiAgICAgICAgICAgIEludFB0ciByZXN1bWU9SW50UHRyLlplcm87CiAgICAgICAgICAgIGludCBzdGF0dXM7CiAgICAgICAgICAgIGRvCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIEludFB0ciBidWZmZXI9SW50UHRyLlplcm87CiAgICAgICAgICAgICAgICBpbnQgZW50cmllc1JlYWQ9MDsKICAgICAgICAgICAgICAgIGludCB0b3RhbEVudHJpZXM9MDsKICAgICAgICAgICAgICAgIHRyeQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHN0YXR1cz1OZXRMb2NhbEdyb3VwR2V0TWVtYmVycyhudWxsLGdyb3VwTmFtZSwwLG91dCBidWZmZXIsTUFYX1BSRUZFUlJFRF9MRU5HVEgsb3V0IGVudHJpZXNSZWFkLG91dCB0b3RhbEVudHJpZXMscmVmIHJlc3VtZSk7CiAgICAgICAgICAgICAgICAgICAgaWYoc3RhdHVzIT0wICYmIHN0YXR1cyE9RVJST1JfTU9SRV9EQVRBKSB0aHJvdyBuZXcgV2luMzJFeGNlcHRpb24oc3RhdHVzKTsKICAgICAgICAgICAgICAgICAgICBmb3IoaW50IGk9MDtpPGVudHJpZXNSZWFkO2krKykKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgIEludFB0ciBzaWRQdHI9TWFyc2hhbC5SZWFkSW50UHRyKGJ1ZmZlcixpKkludFB0ci5TaXplKTsKICAgICAgICAgICAgICAgICAgICAgICAgaWYoc2lkUHRyIT1JbnRQdHIuWmVybykgcmVzdWx0cy5BZGQobmV3IFNlY3VyaXR5SWRlbnRpZmllcihzaWRQdHIpLlZhbHVlKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBmaW5hbGx5IHsgaWYoYnVmZmVyIT1JbnRQdHIuWmVybylOZXRBcGlCdWZmZXJGcmVlKGJ1ZmZlcik7IH0KICAgICAgICAgICAgfSB3aGlsZShzdGF0dXM9PUVSUk9SX01PUkVfREFUQSk7CiAgICAgICAgICAgIHJldHVybiByZXN1bHRzLlRvQXJyYXkoKTsKICAgICAgICB9CiAgICB9Cn0KJ0AKICAgIEFkZC1UeXBlIC1UeXBlRGVmaW5pdGlvbiAkc291cmNlIC1MYW5ndWFnZSBDU2hhcnAgLUVycm9yQWN0aW9uIFN0b3AgfCBPdXQtTnVsbAp9CmZ1bmN0aW9uIEdldC1DYWNoZWRMb2dvblZhbHVlIHsKICAgICRwYXRoPSdIS0xNOlxTT0ZUV0FSRVxNaWNyb3NvZnRcV2luZG93cyBOVFxDdXJyZW50VmVyc2lvblxXaW5sb2dvbicKICAgIHRyeSB7IHJldHVybiBbc3RyaW5nXShHZXQtSXRlbVByb3BlcnR5IC1QYXRoICRwYXRoIC1OYW1lIENhY2hlZExvZ29uc0NvdW50IC1FcnJvckFjdGlvbiBTdG9wKS5DYWNoZWRMb2dvbnNDb3VudCB9IGNhdGNoIHsgcmV0dXJuICRudWxsIH0KfQpmdW5jdGlvbiBHZXQtS2RjUHJveHlTdGF0ZSB7CiAgICAkcm9vdFBhdGg9J0hLTE06XFNPRlRXQVJFXE1pY3Jvc29mdFxXaW5kb3dzXEN1cnJlbnRWZXJzaW9uXFBvbGljaWVzXFN5c3RlbVxLZXJiZXJvcycKICAgICRtYXBwaW5nUGF0aD1Kb2luLVBhdGggJHJvb3RQYXRoICdLZGNQcm94eVNlcnZlcicKICAgICRlbmFibGVkRXhpc3RzPSRmYWxzZTsgJGVuYWJsZWQ9JG51bGw7ICRtYXBwaW5ncz1AKCkKICAgIGlmIChUZXN0LVBhdGggJHJvb3RQYXRoKSB7CiAgICAgICAgJGl0ZW09R2V0LUl0ZW1Qcm9wZXJ0eSAtUGF0aCAkcm9vdFBhdGggLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKICAgICAgICBpZiAoJG51bGwgLW5lICRpdGVtIC1hbmQgJGl0ZW0uUFNPYmplY3QuUHJvcGVydGllcy5OYW1lIC1jb250YWlucyAnS2RjUHJveHlTZXJ2ZXJfRW5hYmxlZCcpIHsgJGVuYWJsZWRFeGlzdHM9JHRydWU7ICRlbmFibGVkPSRpdGVtLktkY1Byb3h5U2VydmVyX0VuYWJsZWQgfQogICAgfQogICAgaWYgKFRlc3QtUGF0aCAkbWFwcGluZ1BhdGgpIHsKICAgICAgICAkbWFwPUdldC1JdGVtUHJvcGVydHkgLVBhdGggJG1hcHBpbmdQYXRoIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlCiAgICAgICAgaWYgKCRudWxsIC1uZSAkbWFwKSB7ICRtYXBwaW5ncz1AKCRtYXAuUFNPYmplY3QuUHJvcGVydGllcyB8IFdoZXJlLU9iamVjdCB7JF8uTmFtZSAtbm90bGlrZSAnUFMqJ30gfCBGb3JFYWNoLU9iamVjdCB7IiQoJF8uTmFtZSk9JChbc3RyaW5nXSRfLlZhbHVlKSJ9IHwgU29ydC1PYmplY3QpIH0KICAgIH0KICAgIFtwc2N1c3RvbW9iamVjdF1Ae2VuYWJsZWRFeGlzdHM9JGVuYWJsZWRFeGlzdHM7ZW5hYmxlZD0kZW5hYmxlZDttYXBwaW5nRXhpc3RzPShUZXN0LVBhdGggJG1hcHBpbmdQYXRoKTttYXBwaW5ncz0kbWFwcGluZ3N9Cn0KCmZ1bmN0aW9uIEdldC1GaXJld2FsbEVuZ2luZVN0YXRlIHsKICAgICRzZXJ2aWNlU3RhdGU9J1VOS05PV04nCiAgICAkZW5hYmxlZFByb2ZpbGVzPUAoKQogICAgJGRpc2FibGVkUHJvZmlsZXM9QCgpCiAgICB0cnkgewogICAgICAgICRzZXJ2aWNlPUdldC1DaW1JbnN0YW5jZSBXaW4zMl9TZXJ2aWNlIC1GaWx0ZXIgIk5hbWU9J01wc1N2YyciIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgJHNlcnZpY2VTdGF0ZT1bc3RyaW5nXSRzZXJ2aWNlLlN0YXRlCiAgICB9IGNhdGNoIHsgfQogICAgdHJ5IHsKICAgICAgICBmb3JlYWNoKCRwcm9maWxlIGluIEAoR2V0LU5ldEZpcmV3YWxsUHJvZmlsZSAtRXJyb3JBY3Rpb24gU3RvcCkpIHsKICAgICAgICAgICAgaWYoW2Jvb2xdJHByb2ZpbGUuRW5hYmxlZCl7JGVuYWJsZWRQcm9maWxlcyArPSBbc3RyaW5nXSRwcm9maWxlLk5hbWV9ZWxzZXskZGlzYWJsZWRQcm9maWxlcyArPSBbc3RyaW5nXSRwcm9maWxlLk5hbWV9CiAgICAgICAgfQogICAgfSBjYXRjaCB7IH0KICAgIFtwc2N1c3RvbW9iamVjdF1AewogICAgICAgIHNlcnZpY2VTdGF0ZT0kc2VydmljZVN0YXRlCiAgICAgICAgZW5hYmxlZFByb2ZpbGVzPUAoJGVuYWJsZWRQcm9maWxlcykKICAgICAgICBkaXNhYmxlZFByb2ZpbGVzPUAoJGRpc2FibGVkUHJvZmlsZXMpCiAgICAgICAgcGFzcz0oJHNlcnZpY2VTdGF0ZSAtZXEgJ1J1bm5pbmcnIC1hbmQgJGVuYWJsZWRQcm9maWxlcy5Db3VudCAtZ3QgMCkKICAgIH0KfQoKZnVuY3Rpb24gTm9ybWFsaXplLVN0YXJ0TW9kZSB7CiAgICBwYXJhbShbc3RyaW5nXSRWYWx1ZSkKICAgIHN3aXRjaCAoJFZhbHVlKSB7ICdBdXRvbWF0aWMnIHsnQXV0byd9ICdBdXRvJyB7J0F1dG8nfSAnTWFudWFsJyB7J01hbnVhbCd9ICdEaXNhYmxlZCcgeydEaXNhYmxlZCd9IGRlZmF1bHQgeyRWYWx1ZX0gfQp9CmZ1bmN0aW9uIENvbXBhcmUtS2RjUHJveHlSZXN0b3JlIHsKICAgIHBhcmFtKCRPcmlnaW5hbCwkQ3VycmVudCkKICAgIGlmICgkbnVsbCAtZXEgJE9yaWdpbmFsKSB7IHJldHVybiAkdHJ1ZSB9CiAgICAkZXhwZWN0ZWRFbmFibGVkRXhpc3RzPVtib29sXShHZXQtUHJvcGVydHlWYWx1ZSAkT3JpZ2luYWwgJ2VuYWJsZWRWYWx1ZUV4aXN0ZWQnICRmYWxzZSkKICAgICRleHBlY3RlZEVuYWJsZWQ9R2V0LVByb3BlcnR5VmFsdWUgJE9yaWdpbmFsICdlbmFibGVkVmFsdWUnICRudWxsCiAgICAkZXhwZWN0ZWRNYXBFeGlzdHM9W2Jvb2xdKEdldC1Qcm9wZXJ0eVZhbHVlICRPcmlnaW5hbCAnbWFwcGluZ1BhdGhFeGlzdGVkJyAkZmFsc2UpCiAgICAkZXhwZWN0ZWRNYXBwaW5ncz1AKEdldC1Qcm9wZXJ0eVZhbHVlICRPcmlnaW5hbCAnbWFwcGluZ3MnIEAoKSB8IEZvckVhY2gtT2JqZWN0IHsiJChbc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkXyAnbmFtZScgJycpKT0kKFtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRfICd2YWx1ZScgJycpKSJ9IHwgU29ydC1PYmplY3QpCiAgICBpZiAoJEN1cnJlbnQuZW5hYmxlZEV4aXN0cyAtbmUgJGV4cGVjdGVkRW5hYmxlZEV4aXN0cykgeyByZXR1cm4gJGZhbHNlIH0KICAgIGlmICgkZXhwZWN0ZWRFbmFibGVkRXhpc3RzIC1hbmQgW3N0cmluZ10kQ3VycmVudC5lbmFibGVkIC1uZSBbc3RyaW5nXSRleHBlY3RlZEVuYWJsZWQpIHsgcmV0dXJuICRmYWxzZSB9CiAgICBpZiAoJEN1cnJlbnQubWFwcGluZ0V4aXN0cyAtbmUgJGV4cGVjdGVkTWFwRXhpc3RzKSB7IHJldHVybiAkZmFsc2UgfQogICAgcmV0dXJuICgoQCgkQ3VycmVudC5tYXBwaW5ncykgLWpvaW4gJ3wnKSAtZXEgKCRleHBlY3RlZE1hcHBpbmdzIC1qb2luICd8JykpCn0KZnVuY3Rpb24gQ29udmVydC1Ub05vcm1hbGl6ZWRTZXQgewogICAgcGFyYW0oJFZhbHVlKQogICAgJGl0ZW1zPUAoKQogICAgZm9yZWFjaCgkZW50cnkgaW4gQCgkVmFsdWUpKSB7CiAgICAgICAgaWYoJG51bGwgLWVxICRlbnRyeSl7Y29udGludWV9CiAgICAgICAgJGl0ZW1zICs9IEAoKFtzdHJpbmddJGVudHJ5IC1zcGxpdCAnLCcpIHwgRm9yRWFjaC1PYmplY3QgeyRfLlRyaW0oKX0gfCBXaGVyZS1PYmplY3QgeyRffSkKICAgIH0KICAgIHJldHVybiBAKCRpdGVtcyB8IFNvcnQtT2JqZWN0IC1VbmlxdWUpCn0KZnVuY3Rpb24gQ29udmVydC1Qcm90b2NvbE5hbWUgewogICAgcGFyYW0oJFZhbHVlKQogICAgJHRleHQ9W3N0cmluZ10kVmFsdWUKICAgIHN3aXRjaCgkdGV4dC5Ub1VwcGVySW52YXJpYW50KCkpIHsnNicgeydUQ1AnfSAnMTcnIHsnVURQJ30gZGVmYXVsdCB7JHRleHQuVG9VcHBlckludmFyaWFudCgpfX0KfQpmdW5jdGlvbiBUZXN0LUZpcmV3YWxsUGxhbiB7CiAgICBwYXJhbSgkUGxhbixbc3RyaW5nXSRPcGVyYXRpb24pCiAgICAkZGVmaW5pdGlvbnM9QChHZXQtUHJvcGVydHlWYWx1ZSAkUGxhbiAncnVsZURlZmluaXRpb25zJyBAKCkpCiAgICBpZiAoJGRlZmluaXRpb25zLkNvdW50IC1lcSAwKSB7IHJldHVybiBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSR0cnVlO0FjdHVhbD0nTm8gcnVsZXMgcmVxdWlyZWQnO0ZhaWx1cmVzPUAoKX0gfQogICAgJGZhaWx1cmVzPUAoKTsgJGFjdHVhbD1AKCkKICAgIGZvcmVhY2goJGRlZmluaXRpb24gaW4gJGRlZmluaXRpb25zKSB7CiAgICAgICAgJG5hbWU9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJGRlZmluaXRpb24gJ25hbWUnICcnKQogICAgICAgICRydWxlPUAoR2V0LU5ldEZpcmV3YWxsUnVsZSAtTmFtZSAkbmFtZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZSkKICAgICAgICBpZigkT3BlcmF0aW9uIC1lcSAnUkVTVE9SRScpIHsKICAgICAgICAgICAgaWYoJHJ1bGUuQ291bnQgLWd0IDApeyRmYWlsdXJlcyArPSAiJHtuYW1lfTpzdGlsbC1wcmVzZW50In0KICAgICAgICAgICAgY29udGludWUKICAgICAgICB9CiAgICAgICAgaWYoJHJ1bGUuQ291bnQgLW5lIDEpeyRmYWlsdXJlcyArPSAiJHtuYW1lfTptaXNzaW5nLW9yLWR1cGxpY2F0ZSI7Y29udGludWV9CiAgICAgICAgJGl0ZW09JHJ1bGVbMF0KICAgICAgICBpZihbc3RyaW5nXSRpdGVtLkVuYWJsZWQgLW5lICdUcnVlJyAtb3IgW3N0cmluZ10kaXRlbS5BY3Rpb24gLW5lICdCbG9jaycgLW9yIFtzdHJpbmddJGl0ZW0uRGlyZWN0aW9uIC1uZSAnT3V0Ym91bmQnKXskZmFpbHVyZXMgKz0gIiR7bmFtZX06cnVsZS1wcm9wZXJ0aWVzIn0KICAgICAgICAkcG9ydEZpbHRlcj1HZXQtTmV0RmlyZXdhbGxQb3J0RmlsdGVyIC1Bc3NvY2lhdGVkTmV0RmlyZXdhbGxSdWxlICRpdGVtIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlCiAgICAgICAgJGFkZHJlc3NGaWx0ZXI9R2V0LU5ldEZpcmV3YWxsQWRkcmVzc0ZpbHRlciAtQXNzb2NpYXRlZE5ldEZpcmV3YWxsUnVsZSAkaXRlbSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQogICAgICAgIGlmKCRudWxsIC1lcSAkcG9ydEZpbHRlciAtb3IgJG51bGwgLWVxICRhZGRyZXNzRmlsdGVyKXskZmFpbHVyZXMgKz0gIiR7bmFtZX06ZmlsdGVyLXVucmVhZGFibGUiO2NvbnRpbnVlfQogICAgICAgICRleHBlY3RlZFByb3RvY29sPUNvbnZlcnQtUHJvdG9jb2xOYW1lIChHZXQtUHJvcGVydHlWYWx1ZSAkZGVmaW5pdGlvbiAncHJvdG9jb2wnICcnKQogICAgICAgICRhY3R1YWxQcm90b2NvbD1Db252ZXJ0LVByb3RvY29sTmFtZSAkcG9ydEZpbHRlci5Qcm90b2NvbAogICAgICAgICRleHBlY3RlZFBvcnRzPUNvbnZlcnQtVG9Ob3JtYWxpemVkU2V0IChHZXQtUHJvcGVydHlWYWx1ZSAkZGVmaW5pdGlvbiAncmVtb3RlUG9ydHMnIEAoKSkKICAgICAgICAkYWN0dWFsUG9ydHM9Q29udmVydC1Ub05vcm1hbGl6ZWRTZXQgJHBvcnRGaWx0ZXIuUmVtb3RlUG9ydAogICAgICAgICRleHBlY3RlZEFkZHJlc3Nlcz1Db252ZXJ0LVRvTm9ybWFsaXplZFNldCAoR2V0LVByb3BlcnR5VmFsdWUgJGRlZmluaXRpb24gJ3JlbW90ZUFkZHJlc3NlcycgQCgpKQogICAgICAgICRhY3R1YWxBZGRyZXNzZXM9Q29udmVydC1Ub05vcm1hbGl6ZWRTZXQgJGFkZHJlc3NGaWx0ZXIuUmVtb3RlQWRkcmVzcwogICAgICAgIGlmKCRhY3R1YWxQcm90b2NvbCAtbmUgJGV4cGVjdGVkUHJvdG9jb2wpeyRmYWlsdXJlcyArPSAiJHtuYW1lfTpwcm90b2NvbCJ9CiAgICAgICAgaWYoKCRhY3R1YWxQb3J0cyAtam9pbiAnfCcpIC1uZSAoJGV4cGVjdGVkUG9ydHMgLWpvaW4gJ3wnKSl7JGZhaWx1cmVzICs9ICIke25hbWV9OnJlbW90ZS1wb3J0cyJ9CiAgICAgICAgaWYoKCRhY3R1YWxBZGRyZXNzZXMgLWpvaW4gJ3wnKSAtbmUgKCRleHBlY3RlZEFkZHJlc3NlcyAtam9pbiAnfCcpKXskZmFpbHVyZXMgKz0gIiR7bmFtZX06cmVtb3RlLWFkZHJlc3NlcyJ9CiAgICAgICAgJGFjdHVhbCArPSAiJHtuYW1lfVske2FjdHVhbFByb3RvY29sfTokKCRhY3R1YWxQb3J0cyAtam9pbiAnLCcpPT4kKCRhY3R1YWxBZGRyZXNzZXMgLWpvaW4gJywnKV0iCiAgICB9CiAgICBbcHNjdXN0b21vYmplY3RdQHtQYXNzPSgkZmFpbHVyZXMuQ291bnQgLWVxIDApO0FjdHVhbD0oJGFjdHVhbCAtam9pbiAnIHwgJyk7RmFpbHVyZXM9JGZhaWx1cmVzfQp9Cgp0cnkgewogICAgaWYgKC1ub3QgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJFN0YXRlUGF0aCkpIHsgZXhpdCAxMCB9CiAgICAkc3RhdGU9R2V0LUNvbnRlbnQgLUxpdGVyYWxQYXRoICRTdGF0ZVBhdGggLVJhdyAtRXJyb3JBY3Rpb24gU3RvcCB8IENvbnZlcnRGcm9tLUpzb24gLUVycm9yQWN0aW9uIFN0b3AKICAgICRwZW5kaW5nPUdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAncG9zdEJvb3RWZXJpZmljYXRpb24nICRudWxsCiAgICAkb3BlcmF0aW9uPVtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRwZW5kaW5nICdvcGVyYXRpb24nICcnKQogICAgaWYgKCRvcGVyYXRpb24gLW5vdGluIEAoJ0xPQ0tET1dOJywnUkVTVE9SRScpKSB7IGV4aXQgMTEgfQoKICAgIEluaXRpYWxpemUtTHNhTmF0aXZlVHlwZQogICAgSW5pdGlhbGl6ZS1XdHNOYXRpdmVUeXBlCiAgICAkY2hlY2tzPUAoKQogICAgJG9zPUdldC1DaW1JbnN0YW5jZSBXaW4zMl9PcGVyYXRpbmdTeXN0ZW0gLUVycm9yQWN0aW9uIFN0b3AKICAgICRjdXJyZW50Qm9vdD0oW2RhdGV0aW1lXSRvcy5MYXN0Qm9vdFVwVGltZSkuVG9Vbml2ZXJzYWxUaW1lKCkKICAgICRib290QmVmb3JlVGV4dCA9IGlmICgkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKSB7IFtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ2FjdGlvbnMnICRudWxsKSAnYm9vdFRpbWVCZWZvcmUnICcnKSB9IGVsc2UgeyBbc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ3Jlc3RvcmVCb290VGltZUJlZm9yZScgJycpIH0KICAgICRib290QmVmb3JlPSRudWxsCiAgICB0cnkgeyBpZiAoLW5vdCBbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRib290QmVmb3JlVGV4dCkpIHsgJGJvb3RCZWZvcmU9W2RhdGV0aW1lXTo6UGFyc2UoJGJvb3RCZWZvcmVUZXh0KS5Ub1VuaXZlcnNhbFRpbWUoKSB9IH0gY2F0Y2ggeyB9CiAgICAkcmVib290UGFzcz0oJG51bGwgLW5lICRib290QmVmb3JlIC1hbmQgJGN1cnJlbnRCb290IC1ndCAkYm9vdEJlZm9yZSkKICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdSRUJPT1QnIC1TdGF0dXMgJChpZigkcmVib290UGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICdDdXJyZW50IGJvb3QgdGltZSBsYXRlciB0aGFuIHByZS1jaGFuZ2UgYm9vdCB0aW1lJyAtQWN0dWFsICRjdXJyZW50Qm9vdC5Ub1N0cmluZygnbycpCgogICAgJGRpcmVjdG9yeUFjbEJlZm9yZT1UZXN0LVNlY3VyZUFjbCAtUGF0aCAkQmFzZURpcmVjdG9yeQogICAgJGRpcmVjdG9yeUFjbEFmdGVyPSRkaXJlY3RvcnlBY2xCZWZvcmUKICAgICRkaXJlY3RvcnlBY2xSZXBhaXJlZD0kZmFsc2UKICAgIGlmKC1ub3QgJGRpcmVjdG9yeUFjbEJlZm9yZS5QYXNzKXsKICAgICAgICB0cnl7U2V0LVNlY3VyZURpcmVjdG9yeUFjbCAtUGF0aCAkQmFzZURpcmVjdG9yeTskZGlyZWN0b3J5QWNsQWZ0ZXI9VGVzdC1TZWN1cmVBY2wgLVBhdGggJEJhc2VEaXJlY3Rvcnk7JGRpcmVjdG9yeUFjbFJlcGFpcmVkPVtib29sXSRkaXJlY3RvcnlBY2xBZnRlci5QYXNzfWNhdGNoe30KICAgIH0KICAgICRkaXJlY3RvcnlBY2xTdGF0dXM9aWYoJGRpcmVjdG9yeUFjbEJlZm9yZS5QYXNzKXsnUEFTUyd9ZWxzZWlmKCRkaXJlY3RvcnlBY2xSZXBhaXJlZCl7J1dBUk5JTkcnfWVsc2V7J0ZBSUwnfQogICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1NUQVRFX0RJUkVDVE9SWV9BQ0wnIC1TdGF0dXMgJGRpcmVjdG9yeUFjbFN0YXR1cyAtRXhwZWN0ZWQgJ093bmVyIFNZU1RFTSwgcHJvdGVjdGVkIEFDTCwgb25seSBTWVNURU0gYW5kIEFkbWluaXN0cmF0b3JzJyAtQWN0dWFsICJCZWZvcmVPd25lcj0kKCRkaXJlY3RvcnlBY2xCZWZvcmUuT3duZXIpO0JlZm9yZVByb3RlY3RlZD0kKCRkaXJlY3RvcnlBY2xCZWZvcmUuUHJvdGVjdGVkKTtCZWZvcmVVbmV4cGVjdGVkPSQoJGRpcmVjdG9yeUFjbEJlZm9yZS5VbmV4cGVjdGVkKTtSZXBhaXJlZD0kZGlyZWN0b3J5QWNsUmVwYWlyZWQ7QWZ0ZXJVbmV4cGVjdGVkPSQoJGRpcmVjdG9yeUFjbEFmdGVyLlVuZXhwZWN0ZWQpIiAtTWVzc2FnZSAkKGlmKCRkaXJlY3RvcnlBY2xSZXBhaXJlZCl7J1VuZXhwZWN0ZWQgZGlyZWN0b3J5IEFDTCBlbnRyaWVzIHdlcmUgcmVtb3ZlZCBieSB0aGUgcG9zdC1ib290IHZlcmlmaWVyLid9ZWxzZXskbnVsbH0pCgogICAgJGFjbEJlZm9yZT1UZXN0LVNlY3VyZUFjbCAtUGF0aCAkU3RhdGVQYXRoCiAgICAkYWNsQWZ0ZXI9JGFjbEJlZm9yZQogICAgJGFjbFJlcGFpcmVkPSRmYWxzZQogICAgaWYoLW5vdCAkYWNsQmVmb3JlLlBhc3MpewogICAgICAgIHRyeXtTZXQtU2VjdXJlRmlsZUFjbCAtUGF0aCAkU3RhdGVQYXRoOyRhY2xBZnRlcj1UZXN0LVNlY3VyZUFjbCAtUGF0aCAkU3RhdGVQYXRoOyRhY2xSZXBhaXJlZD1bYm9vbF0kYWNsQWZ0ZXIuUGFzc31jYXRjaHt9CiAgICB9CiAgICAkYWNsU3RhdHVzPWlmKCRhY2xCZWZvcmUuUGFzcyl7J1BBU1MnfWVsc2VpZigkYWNsUmVwYWlyZWQpeydXQVJOSU5HJ31lbHNleydGQUlMJ30KICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdTVEFURV9GSUxFX0FDTCcgLVN0YXR1cyAkYWNsU3RhdHVzIC1FeHBlY3RlZCAnT3duZXIgU1lTVEVNLCBwcm90ZWN0ZWQgQUNMLCBvbmx5IFNZU1RFTSBhbmQgQWRtaW5pc3RyYXRvcnMnIC1BY3R1YWwgIkJlZm9yZU93bmVyPSQoJGFjbEJlZm9yZS5Pd25lcik7QmVmb3JlUHJvdGVjdGVkPSQoJGFjbEJlZm9yZS5Qcm90ZWN0ZWQpO0JlZm9yZVVuZXhwZWN0ZWQ9JCgkYWNsQmVmb3JlLlVuZXhwZWN0ZWQpO1JlcGFpcmVkPSRhY2xSZXBhaXJlZDtBZnRlclVuZXhwZWN0ZWQ9JCgkYWNsQWZ0ZXIuVW5leHBlY3RlZCkiIC1NZXNzYWdlICQoaWYoJGFjbFJlcGFpcmVkKXsnVW5leHBlY3RlZCBzdGF0ZS1maWxlIEFDTCBlbnRyaWVzIHdlcmUgcmVtb3ZlZCBieSB0aGUgcG9zdC1ib290IHZlcmlmaWVyLid9ZWxzZXskbnVsbH0pCgogICAgJHZlcmlmaWVyQWNsQmVmb3JlPVRlc3QtU2VjdXJlQWNsIC1QYXRoICRQU0NvbW1hbmRQYXRoCiAgICAkdmVyaWZpZXJBY2xBZnRlcj0kdmVyaWZpZXJBY2xCZWZvcmUKICAgICR2ZXJpZmllckFjbFJlcGFpcmVkPSRmYWxzZQogICAgaWYoLW5vdCAkdmVyaWZpZXJBY2xCZWZvcmUuUGFzcyl7CiAgICAgICAgdHJ5e1NldC1TZWN1cmVGaWxlQWNsIC1QYXRoICRQU0NvbW1hbmRQYXRoOyR2ZXJpZmllckFjbEFmdGVyPVRlc3QtU2VjdXJlQWNsIC1QYXRoICRQU0NvbW1hbmRQYXRoOyR2ZXJpZmllckFjbFJlcGFpcmVkPVtib29sXSR2ZXJpZmllckFjbEFmdGVyLlBhc3N9Y2F0Y2h7fQogICAgfQogICAgJHZlcmlmaWVyQWNsU3RhdHVzPWlmKCR2ZXJpZmllckFjbEJlZm9yZS5QYXNzKXsnUEFTUyd9ZWxzZWlmKCR2ZXJpZmllckFjbFJlcGFpcmVkKXsnV0FSTklORyd9ZWxzZXsnRkFJTCd9CiAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnVkVSSUZJRVJfRklMRV9BQ0wnIC1TdGF0dXMgJHZlcmlmaWVyQWNsU3RhdHVzIC1FeHBlY3RlZCAnT3duZXIgU1lTVEVNLCBwcm90ZWN0ZWQgQUNMLCBvbmx5IFNZU1RFTSBhbmQgQWRtaW5pc3RyYXRvcnMnIC1BY3R1YWwgIkJlZm9yZU93bmVyPSQoJHZlcmlmaWVyQWNsQmVmb3JlLk93bmVyKTtCZWZvcmVQcm90ZWN0ZWQ9JCgkdmVyaWZpZXJBY2xCZWZvcmUuUHJvdGVjdGVkKTtCZWZvcmVVbmV4cGVjdGVkPSQoJHZlcmlmaWVyQWNsQmVmb3JlLlVuZXhwZWN0ZWQpO1JlcGFpcmVkPSR2ZXJpZmllckFjbFJlcGFpcmVkO0FmdGVyVW5leHBlY3RlZD0kKCR2ZXJpZmllckFjbEFmdGVyLlVuZXhwZWN0ZWQpIiAtTWVzc2FnZSAkKGlmKCR2ZXJpZmllckFjbFJlcGFpcmVkKXsnVW5leHBlY3RlZCB2ZXJpZmllci1maWxlIEFDTCBlbnRyaWVzIHdlcmUgcmVtb3ZlZCBieSB0aGUgcG9zdC1ib290IHZlcmlmaWVyLid9ZWxzZXskbnVsbH0pCgogICAgJGN1cnJlbnRDYWNoZT1HZXQtQ2FjaGVkTG9nb25WYWx1ZQogICAgaWYgKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicpIHsKICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnQ0FDSEVEX0RPTUFJTl9MT0dPTicgLVN0YXR1cyAkKGlmKCRjdXJyZW50Q2FjaGUgLWVxICcwJyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICcwJyAtQWN0dWFsICQoaWYoJG51bGwgLWVxICRjdXJyZW50Q2FjaGUpeyc8VkFMVUVfQUJTRU5UPid9ZWxzZXskY3VycmVudENhY2hlfSkKICAgIH0gZWxzZSB7CiAgICAgICAgJG9yaWdpbmFsPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ29yaWdpbmFsU3RhdGUnICRudWxsKSAnY2FjaGVkTG9nb25zQ291bnQnICRudWxsCiAgICAgICAgJGV4cGVjdGVkPWlmKFtib29sXShHZXQtUHJvcGVydHlWYWx1ZSAkb3JpZ2luYWwgJ2V4aXN0ZWQnICRmYWxzZSkpe1tzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRvcmlnaW5hbCAndmFsdWUnICcnKX1lbHNleyc8VkFMVUVfQUJTRU5UPid9CiAgICAgICAgJGFjdHVhbD1pZigkbnVsbCAtZXEgJGN1cnJlbnRDYWNoZSl7JzxWQUxVRV9BQlNFTlQ+J31lbHNleyRjdXJyZW50Q2FjaGV9CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0NBQ0hFRF9ET01BSU5fTE9HT04nIC1TdGF0dXMgJChpZigkYWN0dWFsIC1lcSAkZXhwZWN0ZWQpeydQQVNTJ31lbHNleydGQUlMJ30pIC1FeHBlY3RlZCAkZXhwZWN0ZWQgLUFjdHVhbCAkYWN0dWFsCiAgICB9CgogICAgJHJpZ2h0c0ZhaWx1cmVzPUAoKTsgJHJpZ2h0c0NoZWNrZWQ9MDsgJGJhc2VsaW5lV2FybmluZ3M9QCgpOyAkYmFzZWxpbmVDaGVja2VkPTAKICAgICRyZWNvcmRzPUAoR2V0LVByb3BlcnR5VmFsdWUgKEdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAnb3JpZ2luYWxTdGF0ZScgJG51bGwpICd1c2VyUmlnaHRzJyBAKCkpCiAgICBmb3JlYWNoKCRyZWNvcmQgaW4gJHJlY29yZHMpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICAkc2lkPVtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRyZWNvcmQgJ3NpZCcgJycpCiAgICAgICAgICAgIGlmIChbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRzaWQpKSB7IGNvbnRpbnVlIH0KICAgICAgICAgICAgJGFjY291bnRMYWJlbD1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkcmVjb3JkICdhY2NvdW50TmFtZScgJHNpZCkKICAgICAgICAgICAgJGN1cnJlbnRSaWdodHM9QChbRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3ROYXRpdmVWMTQwXTo6R2V0UmlnaHRzKCRzaWQpKQogICAgICAgICAgICBpZigkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKSB7CiAgICAgICAgICAgICAgICBmb3JlYWNoKCRyaWdodCBpbiBAKEdldC1Qcm9wZXJ0eVZhbHVlICRyZWNvcmQgJ3JlcXVpcmVkUmlnaHRzJyBAKCkpKSB7CiAgICAgICAgICAgICAgICAgICAgJHJpZ2h0c0NoZWNrZWQrKwogICAgICAgICAgICAgICAgICAgIGlmKCRyaWdodCAtbm90aW4gJGN1cnJlbnRSaWdodHMpeyRyaWdodHNGYWlsdXJlcyArPSAiJHthY2NvdW50TGFiZWx9OiR7cmlnaHR9Om1pc3NpbmcifQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgZm9yZWFjaCgkcmlnaHQgaW4gQChHZXQtUHJvcGVydHlWYWx1ZSAkcmVjb3JkICdyaWdodHNBZGRlZCcgQCgpKSkgewogICAgICAgICAgICAgICAgICAgICRyaWdodHNDaGVja2VkKysKICAgICAgICAgICAgICAgICAgICBpZigkcmlnaHQgLWluICRjdXJyZW50UmlnaHRzKXskcmlnaHRzRmFpbHVyZXMgKz0gIiR7YWNjb3VudExhYmVsfToke3JpZ2h0fTpzdGlsbC1wcmVzZW50In0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGZvcmVhY2goJHJpZ2h0IGluIEAoR2V0LVByb3BlcnR5VmFsdWUgJHJlY29yZCAncmlnaHRzQmVmb3JlJyBAKCkpKSB7CiAgICAgICAgICAgICAgICAgICAgJGJhc2VsaW5lQ2hlY2tlZCsrCiAgICAgICAgICAgICAgICAgICAgaWYoJHJpZ2h0IC1ub3RpbiAkY3VycmVudFJpZ2h0cyl7JGJhc2VsaW5lV2FybmluZ3MgKz0gIiR7YWNjb3VudExhYmVsfToke3JpZ2h0fTpvcmlnaW5hbC1yaWdodC1taXNzaW5nIn0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggeyAkcmlnaHRzRmFpbHVyZXMgKz0gIiQoW3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJHJlY29yZCAnYWNjb3VudE5hbWUnICd1bmtub3duJykpOmVudW1lcmF0aW9uLWZhaWxlZDokKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSIgfQogICAgfQogICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1VTRVJfUklHSFRTJyAtU3RhdHVzICQoaWYoJHJpZ2h0c0ZhaWx1cmVzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJChpZigkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKXsnQWxsIHJlcXVpcmVkIGRlbnkgcmlnaHRzIHByZXNlbnQnfWVsc2V7J0FsbCBMb2NrZG93bi1hZGRlZCBkZW55IHJpZ2h0cyBhYnNlbnQnfSkgLUFjdHVhbCAkKGlmKCRyaWdodHNGYWlsdXJlcy5Db3VudCAtZXEgMCl7IkNoZWNrZWQgJHJpZ2h0c0NoZWNrZWQgYXNzaWdubWVudHMifWVsc2V7JHJpZ2h0c0ZhaWx1cmVzIC1qb2luICcgfCAnfSkKCiAgICAkc2NvcGVHdWFyZD1HZXQtUHJvcGVydHlWYWx1ZSAoR2V0LVByb3BlcnR5VmFsdWUgJHN0YXRlICdhY3Rpb25zJyAkbnVsbCkgJ3Njb3BlR3VhcmQnICRudWxsCiAgICAkc2NvcGVHdWFyZFByaW5jaXBhbHM9QChHZXQtUHJvcGVydHlWYWx1ZSAkc2NvcGVHdWFyZCAncHJpbmNpcGFscycgQCgpKQogICAgaWYoJG9wZXJhdGlvbiAtZXEgJ0xPQ0tET1dOJyAtYW5kICRzY29wZUd1YXJkUHJpbmNpcGFscy5Db3VudCAtZ3QgMCkgewogICAgICAgICRzY29wZUd1YXJkRmFpbHVyZXM9QCgpCiAgICAgICAgZm9yZWFjaCgkcHJpbmNpcGFsIGluICRzY29wZUd1YXJkUHJpbmNpcGFscykgewogICAgICAgICAgICAkZ3VhcmRTaWQ9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJHByaW5jaXBhbCAnc2lkJyAnJykKICAgICAgICAgICAgaWYoW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkZ3VhcmRTaWQpKXtjb250aW51ZX0KICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICRndWFyZFJpZ2h0cz1AKFtFbWVyZ2VuY3lTZWN1cml0eS5Qb3N0Qm9vdE5hdGl2ZVYxNDBdOjpHZXRSaWdodHMoJGd1YXJkU2lkKSkKICAgICAgICAgICAgICAgIGZvcmVhY2goJHJpZ2h0IGluIEAoJ1NlRGVueUludGVyYWN0aXZlTG9nb25SaWdodCcsJ1NlRGVueVJlbW90ZUludGVyYWN0aXZlTG9nb25SaWdodCcsJ1NlRGVueU5ldHdvcmtMb2dvblJpZ2h0JykpIHsKICAgICAgICAgICAgICAgICAgICBpZigkcmlnaHQgLW5vdGluICRndWFyZFJpZ2h0cyl7JHNjb3BlR3VhcmRGYWlsdXJlcyArPSAiJHtndWFyZFNpZH06JHtyaWdodH06bWlzc2luZyJ9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gY2F0Y2ggeyRzY29wZUd1YXJkRmFpbHVyZXMgKz0gIiR7Z3VhcmRTaWR9OmVudW1lcmF0aW9uLWZhaWxlZDokKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSJ9CiAgICAgICAgfQogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdTQ09QRV9HVUFSRCcgLVN0YXR1cyAkKGlmKCRzY29wZUd1YXJkRmFpbHVyZXMuQ291bnQgLWVxIDApeydQQVNTJ31lbHNleydGQUlMJ30pIC1FeHBlY3RlZCAnQWxsIGJyb2FkIEFMTC1zY29wZSBpbnRlcmFjdGl2ZS9SRFAvbmV0d29yayBndWFyZCByaWdodHMgcHJlc2VudCcgLUFjdHVhbCAkKGlmKCRzY29wZUd1YXJkRmFpbHVyZXMuQ291bnQgLWVxIDApeyIkKCRzY29wZUd1YXJkUHJpbmNpcGFscy5Db3VudCkgZ3VhcmQgcHJpbmNpcGFsKHMpIHZlcmlmaWVkIn1lbHNleyRzY29wZUd1YXJkRmFpbHVyZXMgLWpvaW4gJyB8ICd9KQogICAgfSBlbHNlaWYoJG9wZXJhdGlvbiAtZXEgJ1JFU1RPUkUnIC1hbmQgJHNjb3BlR3VhcmRQcmluY2lwYWxzLkNvdW50IC1ndCAwKSB7CiAgICAgICAgJHNjb3BlR3VhcmRGYWlsdXJlcz1AKCkKICAgICAgICBmb3JlYWNoKCRwcmluY2lwYWwgaW4gJHNjb3BlR3VhcmRQcmluY2lwYWxzKSB7CiAgICAgICAgICAgICRndWFyZFNpZD1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkcHJpbmNpcGFsICdzaWQnICcnKQogICAgICAgICAgICBpZihbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRndWFyZFNpZCkpe2NvbnRpbnVlfQogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgJGd1YXJkUmlnaHRzPUAoW0VtZXJnZW5jeVNlY3VyaXR5LlBvc3RCb290TmF0aXZlVjE0MF06OkdldFJpZ2h0cygkZ3VhcmRTaWQpKQogICAgICAgICAgICAgICAgZm9yZWFjaCgkcmlnaHQgaW4gQCgnU2VEZW55SW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55UmVtb3RlSW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55TmV0d29ya0xvZ29uUmlnaHQnKSkgewogICAgICAgICAgICAgICAgICAgICRyZWNvcmQ9QCgkcmVjb3JkcyB8IFdoZXJlLU9iamVjdCB7W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJF8gJ3NpZCcgJycpIC1lcSAkZ3VhcmRTaWR9IHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkKICAgICAgICAgICAgICAgICAgICBpZigkcmVjb3JkLkNvdW50IC1ndCAwIC1hbmQgJHJpZ2h0IC1pbiBAKEdldC1Qcm9wZXJ0eVZhbHVlICRyZWNvcmRbMF0gJ3JpZ2h0c0FkZGVkJyBAKCkpIC1hbmQgJHJpZ2h0IC1pbiAkZ3VhcmRSaWdodHMpeyRzY29wZUd1YXJkRmFpbHVyZXMgKz0gIiR7Z3VhcmRTaWR9OiR7cmlnaHR9OnN0aWxsLXByZXNlbnQifQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGNhdGNoIHskc2NvcGVHdWFyZEZhaWx1cmVzICs9ICIke2d1YXJkU2lkfTplbnVtZXJhdGlvbi1mYWlsZWQ6JCgkXy5FeGNlcHRpb24uTWVzc2FnZSkifQogICAgICAgIH0KICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnU0NPUEVfR1VBUkQnIC1TdGF0dXMgJChpZigkc2NvcGVHdWFyZEZhaWx1cmVzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ0xvY2tkb3duLWFkZGVkIGJyb2FkIHNjb3BlLWd1YXJkIHJpZ2h0cyBhYnNlbnQnIC1BY3R1YWwgJChpZigkc2NvcGVHdWFyZEZhaWx1cmVzLkNvdW50IC1lcSAwKXsiJCgkc2NvcGVHdWFyZFByaW5jaXBhbHMuQ291bnQpIGd1YXJkIHByaW5jaXBhbChzKSByZXN0b3JlZCJ9ZWxzZXskc2NvcGVHdWFyZEZhaWx1cmVzIC1qb2luICcgfCAnfSkKICAgIH0gZWxzZSB7CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1NDT1BFX0dVQVJEJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgc2NvcGUgZ3VhcmQnIC1BY3R1YWwgJ05vIHNjb3BlIGd1YXJkIHJlY29yZGVkJwogICAgfQoKICAgIGlmKCRvcGVyYXRpb24gLWVxICdSRVNUT1JFJykgewogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdVU0VSX1JJR0hUU19PUklHSU5BTF9CQVNFTElORScgLVN0YXR1cyAkKGlmKCRiYXNlbGluZVdhcm5pbmdzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnV0FSTklORyd9KSAtRXhwZWN0ZWQgJ1ByZS1leGlzdGluZyB1c2VyIHJpZ2h0cyByZW1haW4gcHJlc2VudCcgLUFjdHVhbCAkKGlmKCRiYXNlbGluZVdhcm5pbmdzLkNvdW50IC1lcSAwKXsiQ2hlY2tlZCAkYmFzZWxpbmVDaGVja2VkIG9yaWdpbmFsIGFzc2lnbm1lbnRzIn1lbHNleyRiYXNlbGluZVdhcm5pbmdzIC1qb2luICcgfCAnfSkKICAgIH0KICAgIGVsc2UgewogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdVU0VSX1JJR0hUU19PUklHSU5BTF9CQVNFTElORScgLVN0YXR1cyAnTk9UX0FQUExJQ0FCTEUnIC1FeHBlY3RlZCAnUmVzdG9yZSBvcGVyYXRpb24nIC1BY3R1YWwgJ05vdCBldmFsdWF0ZWQgZHVyaW5nIExvY2tkb3duJwogICAgfQoKICAgIGlmKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicpIHsKICAgICAgICAkcmVzb2x2ZWRUYXJnZXRzPUAoR2V0LVByb3BlcnR5VmFsdWUgJHN0YXRlICd0YXJnZXRzJyBAKCkgfCBXaGVyZS1PYmplY3QgeyRfLnJlc29sdmVTdGF0dXMgLWVxICdTVUNDRVNTJ30pCiAgICAgICAgJHRhcmdldE5hbWVzPUAoKQogICAgICAgIGZvcmVhY2goJHRhcmdldCBpbiAkcmVzb2x2ZWRUYXJnZXRzKSB7CiAgICAgICAgICAgIGZvcmVhY2goJGZpZWxkIGluIEAoJ2FjY291bnROYW1lJywnbm9ybWFsaXplZCcsJ3JlcXVlc3RlZCcpKSB7CiAgICAgICAgICAgICAgICAkdmFsdWU9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJHRhcmdldCAkZmllbGQgJycpCiAgICAgICAgICAgICAgICBpZigtbm90IFtzdHJpbmddOjpJc051bGxPcldoaXRlU3BhY2UoJHZhbHVlKSl7JHRhcmdldE5hbWVzICs9IChOb3JtYWxpemUtU2Vzc2lvbkFjY291bnROYW1lICR2YWx1ZSl9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgJHRhcmdldE5hbWVzPUAoJHRhcmdldE5hbWVzIHwgU2VsZWN0LU9iamVjdCAtVW5pcXVlKQogICAgICAgICRhY3RpdmVTZXNzaW9ucz1AKEdldC1XdHNTZXNzaW9ucykKICAgICAgICAkcmVtYWluaW5nVGFyZ2V0U2Vzc2lvbnM9QCgkYWN0aXZlU2Vzc2lvbnMgfCBXaGVyZS1PYmplY3QgeyhOb3JtYWxpemUtU2Vzc2lvbkFjY291bnROYW1lIChbc3RyaW5nXSRfLmFjY291bnROYW1lKSkgLWluICR0YXJnZXROYW1lc30pCiAgICAgICAgJHNlc3Npb25QYXNzPSgkcmVtYWluaW5nVGFyZ2V0U2Vzc2lvbnMuQ291bnQgLWVxIDApCiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1RBUkdFVF9TRVNTSU9OU19BQlNFTlQnIC1TdGF0dXMgJChpZigkc2Vzc2lvblBhc3MpeydQQVNTJ31lbHNleydGQUlMJ30pIC1FeHBlY3RlZCAnTm8gcmVzb2x2ZWQgdGFyZ2V0IGlkZW50aXR5IGhhcyBhbiBpbnRlcmFjdGl2ZSBzZXNzaW9uIGFmdGVyIHJlYm9vdCcgLUFjdHVhbCAkKGlmKCRzZXNzaW9uUGFzcyl7JzAgdGFyZ2V0IHNlc3Npb25zJ31lbHNleygoJHJlbWFpbmluZ1RhcmdldFNlc3Npb25zIHwgRm9yRWFjaC1PYmplY3QgeyIkKCRfLnNlc3Npb25JZCk6JCgkXy5hY2NvdW50TmFtZSkifSkgLWpvaW4gJyB8ICcpfSkKICAgICAgICAkYWN0aW9ucz1HZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ2FjdGlvbnMnICRudWxsCiAgICAgICAgaWYoJG51bGwgLW5lICRhY3Rpb25zKSB7CiAgICAgICAgICAgICRhYnNlbnRDb3VudD1baW50XVtNYXRoXTo6TWF4KDAsICgkcmVzb2x2ZWRUYXJnZXRzLkNvdW50IC0gJHJlbWFpbmluZ1RhcmdldFNlc3Npb25zLkNvdW50KSkKICAgICAgICAgICAgJHRlbGVtZXRyeT1HZXQtUHJvcGVydHlWYWx1ZSAkYWN0aW9ucyAnc2Vzc2lvblRlbGVtZXRyeScgJG51bGwKICAgICAgICAgICAgaWYoJG51bGwgLWVxICR0ZWxlbWV0cnkpewogICAgICAgICAgICAgICAgJHRlbGVtZXRyeT1bcHNjdXN0b21vYmplY3RdW29yZGVyZWRdQHt0YXJnZXRTZXNzaW9uc0ZvdW5kPTA7bG9nb2ZmUmVxdWVzdHNBY2NlcHRlZD0wO2xvZ29mZlJlcXVlc3RzRmFpbGVkPTA7Y29uZmlybWVkQ2xvc2VkQmVmb3JlUmVib290PTA7cmVtYWluaW5nU2Vzc2lvbklkc0JlZm9yZVJlYm9vdD1AKCk7dGFyZ2V0SWRlbnRpdGllc1dpdGhvdXRTZXNzaW9uQWZ0ZXJSZWJvb3Q9MDtyZW1haW5pbmdUYXJnZXRTZXNzaW9uc0FmdGVyUmVib290PUAoKTtwZW5kaW5nTG9nb2ZmU2Vzc2lvbklkcz1AKCl9CiAgICAgICAgICAgICAgICAkYWN0aW9uc3xBZGQtTWVtYmVyIC1Ob3RlUHJvcGVydHlOYW1lIHNlc3Npb25UZWxlbWV0cnkgLU5vdGVQcm9wZXJ0eVZhbHVlICR0ZWxlbWV0cnkKICAgICAgICAgICAgfQogICAgICAgICAgICAkdGVsZW1ldHJ5LnRhcmdldElkZW50aXRpZXNXaXRob3V0U2Vzc2lvbkFmdGVyUmVib290PSRhYnNlbnRDb3VudAogICAgICAgICAgICAkdGVsZW1ldHJ5LnJlbWFpbmluZ1RhcmdldFNlc3Npb25zQWZ0ZXJSZWJvb3Q9QCgkcmVtYWluaW5nVGFyZ2V0U2Vzc2lvbnMpCiAgICAgICAgfQogICAgfQogICAgZWxzZSB7CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1RBUkdFVF9TRVNTSU9OU19BQlNFTlQnIC1TdGF0dXMgJ05PVF9BUFBMSUNBQkxFJyAtRXhwZWN0ZWQgJ0xvY2tkb3duIG9wZXJhdGlvbicgLUFjdHVhbCAnUmVzdG9yZSBkb2VzIG5vdCBmb3JjZSBhIHVzZXIgc2Vzc2lvbiB0byByZW1haW4gYWJzZW50JwogICAgfQoKICAgICRzY29wZT1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ3Njb3BlJyAnJykKICAgIGlmKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicgLWFuZCAkc2NvcGUgLWVxICdBTExfVVNFUlNfU0FWRV9MQVBTJykgewogICAgICAgICRsYXBzPUdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAnbGFwcycgJG51bGwKICAgICAgICAkbGFwc1NpZD1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkbGFwcyAnYWNjb3VudFNpZCcgJycpCiAgICAgICAgJGxhcHNOYW1lPVtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRsYXBzICdhY2NvdW50TmFtZScgJycpCiAgICAgICAgJGxhcHNGYWlsdXJlcz1AKCkKICAgICAgICB0cnkgewogICAgICAgICAgICBpZihbc3RyaW5nXTo6SXNOdWxsT3JXaGl0ZVNwYWNlKCRsYXBzU2lkKSl7dGhyb3cgJ0xBUFMgYWNjb3VudCBTSUQgaXMgYWJzZW50IGZyb20gc3RhdGUuJ30KICAgICAgICAgICAgJGxvY2FsQWNjb3VudD1AKEdldC1DaW1JbnN0YW5jZSBXaW4zMl9Vc2VyQWNjb3VudCAtRmlsdGVyICdMb2NhbEFjY291bnQ9VHJ1ZScgLUVycm9yQWN0aW9uIFN0b3AgfCBXaGVyZS1PYmplY3QgeyRfLlNJRCAtZXEgJGxhcHNTaWR9IHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkKICAgICAgICAgICAgaWYoJGxvY2FsQWNjb3VudC5Db3VudCAtZXEgMCl7JGxhcHNGYWlsdXJlcyArPSAnYWNjb3VudC1ub3QtZm91bmQnfQogICAgICAgICAgICBlbHNlaWYoW2Jvb2xdJGxvY2FsQWNjb3VudFswXS5EaXNhYmxlZCl7JGxhcHNGYWlsdXJlcyArPSAnYWNjb3VudC1kaXNhYmxlZCd9CiAgICAgICAgICAgICRhZG1pbkdyb3VwPUAoR2V0LUNpbUluc3RhbmNlIFdpbjMyX0dyb3VwIC1GaWx0ZXIgIkxvY2FsQWNjb3VudD1UcnVlIEFORCBTSUQ9J1MtMS01LTMyLTU0NCciIC1FcnJvckFjdGlvbiBTdG9wIHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkKICAgICAgICAgICAgJGFkbWluU2lkcz1AKCkKICAgICAgICAgICAgaWYoJGFkbWluR3JvdXAuQ291bnQgLWd0IDApewogICAgICAgICAgICAgICAgdHJ5eyRhZG1pblNpZHM9QChbRW1lcmdlbmN5U2VjdXJpdHkuUG9zdEJvb3ROYXRpdmVWMTQwXTo6R2V0TG9jYWxHcm91cE1lbWJlclNpZHMoW3N0cmluZ10kYWRtaW5Hcm91cFswXS5OYW1lKSl9Y2F0Y2h7fQogICAgICAgICAgICAgICAgaWYoJGFkbWluU2lkcy5Db3VudCAtZXEgMCl7CiAgICAgICAgICAgICAgICAgICAgdHJ5eyRhZG1pblNpZHM9QChHZXQtQ2ltQXNzb2NpYXRlZEluc3RhbmNlIC1JbnB1dE9iamVjdCAkYWRtaW5Hcm91cFswXSAtQXNzb2NpYXRpb24gV2luMzJfR3JvdXBVc2VyIC1FcnJvckFjdGlvbiBTdG9wIHwgRm9yRWFjaC1PYmplY3Qge1tzdHJpbmddJF8uU0lEfSl9Y2F0Y2h7fQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmKCRsYXBzU2lkIC1ub3RpbiAkYWRtaW5TaWRzKXskbGFwc0ZhaWx1cmVzICs9ICdub3QtbG9jYWwtYWRtaW5pc3RyYXRvcid9CiAgICAgICAgICAgICRsYXBzUmlnaHRzPUAoW0VtZXJnZW5jeVNlY3VyaXR5LlBvc3RCb290TmF0aXZlVjE0MF06OkdldFJpZ2h0cygkbGFwc1NpZCkpCiAgICAgICAgICAgICRibG9ja2luZz1AKCRsYXBzUmlnaHRzIHwgV2hlcmUtT2JqZWN0IHskXyAtaW4gQCgnU2VEZW55SW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55UmVtb3RlSW50ZXJhY3RpdmVMb2dvblJpZ2h0JywnU2VEZW55TmV0d29ya0xvZ29uUmlnaHQnKX0pCiAgICAgICAgICAgIGlmKCRibG9ja2luZy5Db3VudCAtZ3QgMCl7JGxhcHNGYWlsdXJlcyArPSAoJ2Jsb2NraW5nLXJpZ2h0cz0nICsgKCRibG9ja2luZyAtam9pbiAnLCcpKX0KICAgICAgICB9CiAgICAgICAgY2F0Y2ggeyRsYXBzRmFpbHVyZXMgKz0gKCd2ZXJpZmljYXRpb24tZXJyb3I9JyArICRfLkV4Y2VwdGlvbi5NZXNzYWdlKX0KICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnU0FWRUxBUFNfUkVDT1ZFUllfSURFTlRJVFknIC1TdGF0dXMgJChpZigkbGFwc0ZhaWx1cmVzLkNvdW50IC1lcSAwKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ1ZlcmlmaWVkIExBUFMgYWNjb3VudCBleGlzdHMsIGlzIGVuYWJsZWQsIGlzIGxvY2FsIGFkbWluaXN0cmF0b3IsIGFuZCBoYXMgbm8gaHVtYW4tYWNjZXNzIGRlbnkgcmlnaHRzJyAtQWN0dWFsICQoaWYoJGxhcHNGYWlsdXJlcy5Db3VudCAtZXEgMCl7IiR7bGFwc05hbWV9OyR7bGFwc1NpZH07dXNhYmxlIn1lbHNleyRsYXBzRmFpbHVyZXMgLWpvaW4gJyB8ICd9KQogICAgfQogICAgZWxzZSB7CiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ1NBVkVMQVBTX1JFQ09WRVJZX0lERU5USVRZJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgLVNhdmVMYXBzIExvY2tkb3duJyAtQWN0dWFsICdOb3QgcmVxdWVzdGVkIG9yIFJlc3RvcmUgb3BlcmF0aW9uJwogICAgfQoKICAgICRhbGxTY29wZT0kc2NvcGUgLWluIEAoJ0FMTF9VU0VSUycsJ0FMTF9VU0VSU19TQVZFX0xBUFMnKQogICAgJGRvbWFpbkNvbnRleHQ9R2V0LVByb3BlcnR5VmFsdWUgKEdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAnYWN0aW9ucycgJG51bGwpICdkb21haW5Db250ZXh0JyAkbnVsbAogICAgJHBhcnRPZkRvbWFpbj1bYm9vbF0oR2V0LVByb3BlcnR5VmFsdWUgJGRvbWFpbkNvbnRleHQgJ3BhcnRPZkRvbWFpbicgJGZhbHNlKQogICAgaWYoJGFsbFNjb3BlIC1hbmQgJHBhcnRPZkRvbWFpbikgewogICAgICAgICRwbGFuPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ2FjdGlvbnMnICRudWxsKSAnYXV0aEZpcmV3YWxsJyAkbnVsbAogICAgICAgIGlmKCRvcGVyYXRpb24gLWVxICdMT0NLRE9XTicpIHsKICAgICAgICAgICAgJGZpcmV3YWxsRW5naW5lPUdldC1GaXJld2FsbEVuZ2luZVN0YXRlCiAgICAgICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdGSVJFV0FMTF9FTkdJTkUnIC1TdGF0dXMgJChpZigkZmlyZXdhbGxFbmdpbmUucGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICdNcHNTdmMgcnVubmluZyBhbmQgYXQgbGVhc3Qgb25lIFdpbmRvd3MgRmlyZXdhbGwgcHJvZmlsZSBlbmFibGVkJyAtQWN0dWFsICJTZXJ2aWNlPSQoJGZpcmV3YWxsRW5naW5lLnNlcnZpY2VTdGF0ZSk7RW5hYmxlZD0kKCRmaXJld2FsbEVuZ2luZS5lbmFibGVkUHJvZmlsZXMgLWpvaW4gJywnKTtEaXNhYmxlZD0kKCRmaXJld2FsbEVuZ2luZS5kaXNhYmxlZFByb2ZpbGVzIC1qb2luICcsJykiCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0ZJUkVXQUxMX0VOR0lORScgLVN0YXR1cyAnTk9UX0FQUExJQ0FCTEUnIC1FeHBlY3RlZCAnTG9ja2Rvd24gb3BlcmF0aW9uJyAtQWN0dWFsICdSZXN0b3JlIGRvZXMgbm90IGNoYW5nZSBmaXJld2FsbCBlbmdpbmUgc3RhdGUnCiAgICAgICAgfQogICAgICAgICRmaXJld2FsbFJlc3VsdD1UZXN0LUZpcmV3YWxsUGxhbiAtUGxhbiAkcGxhbiAtT3BlcmF0aW9uICRvcGVyYXRpb24KICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnQVVUSF9GSVJFV0FMTCcgLVN0YXR1cyAkKGlmKCRmaXJld2FsbFJlc3VsdC5QYXNzKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJChpZigkb3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKXsnQWxsIHBsYW5uZWQgZW1lcmdlbmN5IGJsb2NrIHJ1bGVzIGFjdGl2ZSd9ZWxzZXsnQWxsIGVtZXJnZW5jeSBibG9jayBydWxlcyBhYnNlbnQnfSkgLUFjdHVhbCAkKGlmKCRmaXJld2FsbFJlc3VsdC5GYWlsdXJlcy5Db3VudCAtZXEgMCl7JGZpcmV3YWxsUmVzdWx0LkFjdHVhbH1lbHNleyRmaXJld2FsbFJlc3VsdC5GYWlsdXJlcyAtam9pbiAnIHwgJ30pCgogICAgICAgICRuZXRsb2dvbj1HZXQtQ2ltSW5zdGFuY2UgV2luMzJfU2VydmljZSAtRmlsdGVyICJOYW1lPSdOZXRsb2dvbiciIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgaWYoJG9wZXJhdGlvbiAtZXEgJ1JFU1RPUkUnKSB7CiAgICAgICAgICAgICRvcmlnaW5hbE5ldGxvZ29uPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ29yaWdpbmFsU3RhdGUnICRudWxsKSAnbmV0bG9nb24nICRudWxsCiAgICAgICAgICAgICRleHBlY3RlZFN0YXJ0PU5vcm1hbGl6ZS1TdGFydE1vZGUgKFtzdHJpbmddKEdldC1Qcm9wZXJ0eVZhbHVlICRvcmlnaW5hbE5ldGxvZ29uICdzdGFydE1vZGUnICdNYW51YWwnKSkKICAgICAgICAgICAgJGV4cGVjdGVkU3RhdGU9W3N0cmluZ10oR2V0LVByb3BlcnR5VmFsdWUgJG9yaWdpbmFsTmV0bG9nb24gJ3N0YXRlJyAnU3RvcHBlZCcpCiAgICAgICAgICAgIGlmKCRleHBlY3RlZFN0YXRlIC1lcSAnUnVubmluZycgLWFuZCAkbmV0bG9nb24uU3RhdGUgLW5lICdSdW5uaW5nJyAtYW5kICRleHBlY3RlZFN0YXJ0IC1uZSAnRGlzYWJsZWQnKSB7CiAgICAgICAgICAgICAgICB0cnkgeyBTdGFydC1TZXJ2aWNlIE5ldGxvZ29uIC1FcnJvckFjdGlvbiBTdG9wOyBTdGFydC1TbGVlcCAtU2Vjb25kcyAyIH0gY2F0Y2ggeyB9CiAgICAgICAgICAgICAgICAkbmV0bG9nb249R2V0LUNpbUluc3RhbmNlIFdpbjMyX1NlcnZpY2UgLUZpbHRlciAiTmFtZT0nTmV0bG9nb24nIiAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgICAgICB9CiAgICAgICAgICAgICRuZXRQYXNzPSgoTm9ybWFsaXplLVN0YXJ0TW9kZSAoW3N0cmluZ10kbmV0bG9nb24uU3RhcnRNb2RlKSkgLWVxICRleHBlY3RlZFN0YXJ0IC1hbmQgKCgkZXhwZWN0ZWRTdGF0ZSAtbmUgJ1J1bm5pbmcnKSAtb3IgJG5ldGxvZ29uLlN0YXRlIC1lcSAnUnVubmluZycpKQogICAgICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnTkVUTE9HT04nIC1TdGF0dXMgJChpZigkbmV0UGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICIkZXhwZWN0ZWRTdGF0ZS8kZXhwZWN0ZWRTdGFydCIgLUFjdHVhbCAiJCgkbmV0bG9nb24uU3RhdGUpLyQoTm9ybWFsaXplLVN0YXJ0TW9kZSAoW3N0cmluZ10kbmV0bG9nb24uU3RhcnRNb2RlKSkiCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJG5ldFBhc3M9KCRuZXRsb2dvbi5TdGF0ZSAtZXEgJ1N0b3BwZWQnIC1hbmQgKE5vcm1hbGl6ZS1TdGFydE1vZGUgKFtzdHJpbmddJG5ldGxvZ29uLlN0YXJ0TW9kZSkpIC1lcSAnRGlzYWJsZWQnKQogICAgICAgICAgICAkY2hlY2tzICs9IE5ldy1DaGVjayAtTmFtZSAnTkVUTE9HT04nIC1TdGF0dXMgJChpZigkbmV0UGFzcyl7J1BBU1MnfWVsc2V7J0ZBSUwnfSkgLUV4cGVjdGVkICdTdG9wcGVkL0Rpc2FibGVkJyAtQWN0dWFsICIkKCRuZXRsb2dvbi5TdGF0ZSkvJChOb3JtYWxpemUtU3RhcnRNb2RlIChbc3RyaW5nXSRuZXRsb2dvbi5TdGFydE1vZGUpKSIKICAgICAgICB9CgogICAgICAgICRjdXJyZW50S2RjPUdldC1LZGNQcm94eVN0YXRlCiAgICAgICAgaWYoJG9wZXJhdGlvbiAtZXEgJ0xPQ0tET1dOJykgewogICAgICAgICAgICAka2RjUGFzcz0oJGN1cnJlbnRLZGMuZW5hYmxlZEV4aXN0cyAtYW5kIFtpbnRdJGN1cnJlbnRLZGMuZW5hYmxlZCAtZXEgMCAtYW5kIC1ub3QgJGN1cnJlbnRLZGMubWFwcGluZ0V4aXN0cykKICAgICAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0tEQ19QUk9YWScgLVN0YXR1cyAkKGlmKCRrZGNQYXNzKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ0Rpc2FibGVkIGFuZCBtYXBwaW5ncyBhYnNlbnQnIC1BY3R1YWwgIkVuYWJsZWQ9JCgkY3VycmVudEtkYy5lbmFibGVkKTtNYXBwaW5nRXhpc3RzPSQoJGN1cnJlbnRLZGMubWFwcGluZ0V4aXN0cykiCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJG9yaWdpbmFsS2RjPUdldC1Qcm9wZXJ0eVZhbHVlIChHZXQtUHJvcGVydHlWYWx1ZSAkc3RhdGUgJ29yaWdpbmFsU3RhdGUnICRudWxsKSAna2RjUHJveHknICRudWxsCiAgICAgICAgICAgICRrZGNQYXNzPUNvbXBhcmUtS2RjUHJveHlSZXN0b3JlIC1PcmlnaW5hbCAkb3JpZ2luYWxLZGMgLUN1cnJlbnQgJGN1cnJlbnRLZGMKICAgICAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0tEQ19QUk9YWScgLVN0YXR1cyAkKGlmKCRrZGNQYXNzKXsnUEFTUyd9ZWxzZXsnRkFJTCd9KSAtRXhwZWN0ZWQgJ09yaWdpbmFsIEtEQyBwcm94eSBjb25maWd1cmF0aW9uJyAtQWN0dWFsICJFbmFibGVkRXhpc3RzPSQoJGN1cnJlbnRLZGMuZW5hYmxlZEV4aXN0cyk7RW5hYmxlZD0kKCRjdXJyZW50S2RjLmVuYWJsZWQpO01hcHBpbmdFeGlzdHM9JCgkY3VycmVudEtkYy5tYXBwaW5nRXhpc3RzKSIKICAgICAgICB9CiAgICB9IGVsc2UgewogICAgICAgICRjaGVja3MgKz0gTmV3LUNoZWNrIC1OYW1lICdBVVRIX0ZJUkVXQUxMJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgc2NvcGUgb24gYSBkb21haW4tam9pbmVkIGRldmljZScgLUFjdHVhbCAnTm90IGNvbmZpZ3VyZWQnCiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ05FVExPR09OJyAtU3RhdHVzICdOT1RfQVBQTElDQUJMRScgLUV4cGVjdGVkICdBTEwgc2NvcGUgb24gYSBkb21haW4tam9pbmVkIGRldmljZScgLUFjdHVhbCAnTm90IGNoYW5nZWQnCiAgICAgICAgJGNoZWNrcyArPSBOZXctQ2hlY2sgLU5hbWUgJ0tEQ19QUk9YWScgLVN0YXR1cyAnTk9UX0FQUExJQ0FCTEUnIC1FeHBlY3RlZCAnQUxMIHNjb3BlIG9uIGEgZG9tYWluLWpvaW5lZCBkZXZpY2UnIC1BY3R1YWwgJ05vdCBjaGFuZ2VkJwogICAgfQoKICAgICRmYWlsZWQ9QCgkY2hlY2tzIHwgV2hlcmUtT2JqZWN0IHskXy5zdGF0dXMgLWVxICdGQUlMJ30pCiAgICAkd2FybmluZ0NoZWNrcz1AKCRjaGVja3MgfCBXaGVyZS1PYmplY3QgeyRfLnN0YXR1cyAtZXEgJ1dBUk5JTkcnfSkKICAgICRvdmVyYWxsPWlmKCRmYWlsZWQuQ291bnQgLWd0IDApeydGQUlMRURfVkVSSUZJQ0FUSU9OJ31lbHNlaWYoJHdhcm5pbmdDaGVja3MuQ291bnQgLWd0IDApeydTVUNDRVNTX1dJVEhfV0FSTklOR1MnfWVsc2V7J1NVQ0NFU1MnfQogICAgJHZlcmlmaWNhdGlvbj1bcHNjdXN0b21vYmplY3RdW29yZGVyZWRdQHsKICAgICAgICBvcGVyYXRpb249JG9wZXJhdGlvbjtzdGF0dXM9J0NPTVBMRVRFRCc7b3ZlcmFsbFN0YXR1cz0kb3ZlcmFsbAogICAgICAgIHNjaGVkdWxlZFV0Yz1HZXQtUHJvcGVydHlWYWx1ZSAkcGVuZGluZyAnc2NoZWR1bGVkVXRjJyAkbnVsbDt2ZXJpZmllZFV0Yz1HZXQtVXRjVGltZXN0YW1wCiAgICAgICAgY3VycmVudEJvb3RUaW1lPSRjdXJyZW50Qm9vdC5Ub1N0cmluZygnbycpO3JlYm9vdFZlcmlmaWVkPSRyZWJvb3RQYXNzCiAgICAgICAgcGFzc2VkQ2hlY2tDb3VudD1AKCRjaGVja3MgfCBXaGVyZS1PYmplY3QgeyRfLnN0YXR1cyAtZXEgJ1BBU1MnfSkuQ291bnQKICAgICAgICB3YXJuaW5nQ2hlY2tDb3VudD0kd2FybmluZ0NoZWNrcy5Db3VudDt3YXJuaW5nQ2hlY2tzPSgoJHdhcm5pbmdDaGVja3MgfCBGb3JFYWNoLU9iamVjdCB7JF8ubmFtZX0pIC1qb2luICc7JykKICAgICAgICBmYWlsZWRDaGVja0NvdW50PSRmYWlsZWQuQ291bnQ7ZmFpbGVkQ2hlY2tzPSgoJGZhaWxlZCB8IEZvckVhY2gtT2JqZWN0IHskXy5uYW1lfSkgLWpvaW4gJzsnKQogICAgICAgIHRhc2tOYW1lPSRUYXNrTmFtZTtzY3JpcHRQYXRoPSRQU0NvbW1hbmRQYXRoO2NoZWNrcz0kY2hlY2tzCiAgICB9CiAgICBpZigkc3RhdGUuUFNPYmplY3QuUHJvcGVydGllcy5OYW1lIC1jb250YWlucyAncG9zdEJvb3RWZXJpZmljYXRpb24nKXskc3RhdGUucG9zdEJvb3RWZXJpZmljYXRpb249JHZlcmlmaWNhdGlvbn1lbHNleyRzdGF0ZXxBZGQtTWVtYmVyIC1Ob3RlUHJvcGVydHlOYW1lIHBvc3RCb290VmVyaWZpY2F0aW9uIC1Ob3RlUHJvcGVydHlWYWx1ZSAkdmVyaWZpY2F0aW9ufQogICAgaWYoJG9wZXJhdGlvbiAtZXEgJ0xPQ0tET1dOJyl7CiAgICAgICAgJHN0YXRlLnN0YXR1cz1pZigkb3ZlcmFsbCAtZXEgJ1NVQ0NFU1MnKXsnQUNUSVZFX1ZFUklGSUVEJ31lbHNlaWYoJG92ZXJhbGwgLWVxICdTVUNDRVNTX1dJVEhfV0FSTklOR1MnKXsnQUNUSVZFX1ZFUklGSUVEX1dJVEhfV0FSTklOR1MnfWVsc2V7J0xPQ0tET1dOX1BBUlRJQUwnfQogICAgfQogICAgaWYoJG9wZXJhdGlvbiAtZXEgJ1JFU1RPUkUnKXsKICAgICAgICAkc3RhdGUuc3RhdHVzPWlmKCRvdmVyYWxsIC1lcSAnU1VDQ0VTUycpeydSRVNUT1JFRF9WRVJJRklFRCd9ZWxzZWlmKCRvdmVyYWxsIC1lcSAnU1VDQ0VTU19XSVRIX1dBUk5JTkdTJyl7J1JFU1RPUkVEX1ZFUklGSUVEX1dJVEhfV0FSTklOR1MnfWVsc2V7J1JFU1RPUkVfUEFSVElBTCd9CiAgICB9CiAgICBXcml0ZS1TdGF0ZSAtU3RhdGUgJHN0YXRlCiAgICAkc2NyaXB0OlZlcmlmaWNhdGlvblJlY29yZGVkPSR0cnVlCn0KY2F0Y2ggewogICAgdHJ5IHsKICAgICAgICBpZihUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRTdGF0ZVBhdGgpIHsKICAgICAgICAgICAgJHN0YXRlPUdldC1Db250ZW50IC1MaXRlcmFsUGF0aCAkU3RhdGVQYXRoIC1SYXcgLUVycm9yQWN0aW9uIFN0b3AgfCBDb252ZXJ0RnJvbS1Kc29uIC1FcnJvckFjdGlvbiBTdG9wCiAgICAgICAgICAgICRwZW5kaW5nPUdldC1Qcm9wZXJ0eVZhbHVlICRzdGF0ZSAncG9zdEJvb3RWZXJpZmljYXRpb24nICRudWxsCiAgICAgICAgICAgICRmYWlsdXJlPVtwc2N1c3RvbW9iamVjdF1bb3JkZXJlZF1Ae29wZXJhdGlvbj1bc3RyaW5nXShHZXQtUHJvcGVydHlWYWx1ZSAkcGVuZGluZyAnb3BlcmF0aW9uJyAnVU5LTk9XTicpO3N0YXR1cz0nRkFJTEVEJztvdmVyYWxsU3RhdHVzPSdGQUlMRURfVkVSSUZJQ0FUSU9OJzt2ZXJpZmllZFV0Yz1HZXQtVXRjVGltZXN0YW1wO3JlYm9vdFZlcmlmaWVkPSRmYWxzZTtwYXNzZWRDaGVja0NvdW50PTA7d2FybmluZ0NoZWNrQ291bnQ9MDt3YXJuaW5nQ2hlY2tzPScnO2ZhaWxlZENoZWNrQ291bnQ9MTtmYWlsZWRDaGVja3M9J1BPU1RCT09UX1ZFUklGSUVSX0VYQ0VQVElPTic7ZXJyb3JNZXNzYWdlPSRfLkV4Y2VwdGlvbi5NZXNzYWdlO3Rhc2tOYW1lPSRUYXNrTmFtZTtzY3JpcHRQYXRoPSRQU0NvbW1hbmRQYXRoO2NoZWNrcz1AKCl9CiAgICAgICAgICAgIGlmKCRzdGF0ZS5QU09iamVjdC5Qcm9wZXJ0aWVzLk5hbWUgLWNvbnRhaW5zICdwb3N0Qm9vdFZlcmlmaWNhdGlvbicpeyRzdGF0ZS5wb3N0Qm9vdFZlcmlmaWNhdGlvbj0kZmFpbHVyZX1lbHNleyRzdGF0ZXxBZGQtTWVtYmVyIC1Ob3RlUHJvcGVydHlOYW1lIHBvc3RCb290VmVyaWZpY2F0aW9uIC1Ob3RlUHJvcGVydHlWYWx1ZSAkZmFpbHVyZX0KICAgICAgICAgICAgaWYoJGZhaWx1cmUub3BlcmF0aW9uIC1lcSAnTE9DS0RPV04nKXskc3RhdGUuc3RhdHVzPSdMT0NLRE9XTl9QQVJUSUFMJ30KICAgICAgICAgICAgaWYoJGZhaWx1cmUub3BlcmF0aW9uIC1lcSAnUkVTVE9SRScpeyRzdGF0ZS5zdGF0dXM9J1JFU1RPUkVfUEFSVElBTCd9CiAgICAgICAgICAgIFdyaXRlLVN0YXRlIC1TdGF0ZSAkc3RhdGUKICAgICAgICAgICAgJHNjcmlwdDpWZXJpZmljYXRpb25SZWNvcmRlZD0kdHJ1ZQogICAgICAgIH0KICAgIH0gY2F0Y2ggeyB9Cn0KZmluYWxseSB7CiAgICBpZigkc2NyaXB0OlZlcmlmaWNhdGlvblJlY29yZGVkKSB7CiAgICAgICAgdHJ5IHsgJiAiJGVudjpTeXN0ZW1Sb290XFN5c3RlbTMyXHNjaHRhc2tzLmV4ZSIgL0RlbGV0ZSAvVE4gJFRhc2tOYW1lIC9GIHwgT3V0LU51bGwgfSBjYXRjaCB7IH0KICAgICAgICB0cnkgeyBSZW1vdmUtSXRlbSAtTGl0ZXJhbFBhdGggJFBTQ29tbWFuZFBhdGggLUZvcmNlIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIH0gY2F0Y2ggeyB9CiAgICB9Cn0K'
$script:State=$null
$script:RestoreStages=@();$script:Warnings=@();$script:Errors=@()
function Get-UtcTimestamp { return [DateTime]::UtcNow.ToString('o') }
function Set-SecureDirectoryAcl {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { $null = New-Item -Path $Path -ItemType Directory -Force }
$systemSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
$administratorsSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
$inheritance = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$propagation = [System.Security.AccessControl.PropagationFlags]::None
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$fullControl = [System.Security.AccessControl.FileSystemRights]::FullControl
$security = [System.Security.AccessControl.DirectorySecurity]::new()
$security.SetAccessRuleProtection($true, $false)
$security.SetOwner($systemSid)
$security.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($systemSid, $fullControl, $inheritance, $propagation, $allow))
$security.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($administratorsSid, $fullControl, $inheritance, $propagation, $allow))
[System.IO.DirectoryInfo]::new($Path).SetAccessControl($security)
}
function Set-SecureFileAcl {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return }
$systemSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
$administratorsSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$fullControl = [System.Security.AccessControl.FileSystemRights]::FullControl
$security = [System.Security.AccessControl.FileSecurity]::new()
$security.SetAccessRuleProtection($true, $false)
$security.SetOwner($systemSid)
$security.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($systemSid, $fullControl, $allow))
$security.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($administratorsSid, $fullControl, $allow))
[System.IO.FileInfo]::new($Path).SetAccessControl($security)
}
function Write-State {
if ($null -eq $script:State) { return }
Set-SecureDirectoryAcl -Path $BaseDirectory
if ($script:State.PSObject.Properties.Name -contains 'restoreStages') {
$script:State.restoreStages = $script:RestoreStages
}
else {
$script:State | Add-Member -NotePropertyName restoreStages -NotePropertyValue $script:RestoreStages
}
if ($script:State.PSObject.Properties.Name -contains 'restoreWarnings') {
$script:State.restoreWarnings = $script:Warnings
}
else {
$script:State | Add-Member -NotePropertyName restoreWarnings -NotePropertyValue $script:Warnings
}
if ($script:State.PSObject.Properties.Name -contains 'restoreErrors') {
$script:State.restoreErrors = $script:Errors
}
else {
$script:State | Add-Member -NotePropertyName restoreErrors -NotePropertyValue $script:Errors
}
if([string]$script:State.operation -eq 'RESTORE'){
$script:State.warnings=@($script:Warnings)
$script:State.errors=@($script:Errors)
}
$json = $script:State | ConvertTo-Json -Depth 24
[System.IO.File]::WriteAllText($TemporaryStatePath, $json, [System.Text.UTF8Encoding]::new($false))
Set-SecureFileAcl -Path $TemporaryStatePath
Move-Item -LiteralPath $TemporaryStatePath -Destination $StatePath -Force
Set-SecureFileAcl -Path $StatePath
}
function Add-RestoreStage {
param([string]$Name,[ValidateSet('SUCCESS','WARNING','FAILED','SKIPPED','NOT_APPLICABLE')][string]$Status,[datetime]$Started,[int]$AffectedCount=0,[string]$ErrorCode=$null,[string]$ErrorMessage=$null,[string]$Message=$null,[string]$Expected=$null,[string]$Actual=$null,[ValidateSet('PASS','WARNING','FAIL','PENDING','NOT_CHECKED','NOT_APPLICABLE')][string]$Verification='NOT_CHECKED')
if($null -eq $Started){$Started=[DateTime]::UtcNow};if($Verification -eq 'NOT_CHECKED'){$Verification=switch($Status){'SUCCESS'{'PASS'}'WARNING'{'WARNING'}'FAILED'{'FAIL'}'NOT_APPLICABLE'{'NOT_APPLICABLE'}default{'NOT_CHECKED'}}}
$completed=[DateTime]::UtcNow;$script:RestoreStages += [pscustomobject][ordered]@{name=$Name;status=$Status;verification=$Verification;startedUtc=$Started.ToString('o');completedUtc=$completed.ToString('o');durationMs=[int][Math]::Max(0,($completed-$Started).TotalMilliseconds);affectedCount=$AffectedCount;expected=$Expected;actual=$Actual;errorCode=$ErrorCode;errorMessage=$ErrorMessage;message=$Message}
if($Status -eq 'WARNING' -and $ErrorCode){$script:Warnings += [pscustomobject]@{code=$ErrorCode;message=$ErrorMessage;timestampUtc=Get-UtcTimestamp}}
if($Status -eq 'FAILED' -and $ErrorCode){$script:Errors += [pscustomobject]@{code=$ErrorCode;message=$ErrorMessage;stage=$Name;timestampUtc=Get-UtcTimestamp}}
Write-State
}
function Get-RestoreStageStatus {param([string]$Name);$item=@($script:RestoreStages | Where-Object {$_.name -eq $Name} | Select-Object -Last 1);if($item.Count -eq 0){return 'NOT_REACHED'};[string]$item[0].status}
function Convert-RestoreStatusForRemarks {param([string]$Status);switch($Status){'SUCCESS'{'PASS'}'WARNING'{'WARN'}'FAILED'{'FAIL'}'NOT_APPLICABLE'{'N/A'}'SKIPPED'{'SKIP'}default{'NOT_REACHED'}}}
function Get-CombinedRestoreCheck {param([string[]]$Stages);$statuses=@($Stages | ForEach-Object {Get-RestoreStageStatus $_});if('FAILED' -in $statuses){return 'FAIL'};if('WARNING' -in $statuses){return 'WARN'};if(@($statuses | Where-Object {$_ -eq 'SUCCESS'}).Count -gt 0 -and @($statuses | Where-Object {$_ -eq 'NOT_REACHED'}).Count -eq 0){return 'PASS'};if(@($statuses | Where-Object {$_ -eq 'NOT_APPLICABLE'}).Count -eq $statuses.Count){return 'N/A'};if('SKIPPED' -in $statuses){return 'SKIP'};'NOT_REACHED'}
function Write-Remarks {
$failed=@($script:RestoreStages | Where-Object {$_.status -eq 'FAILED'} | ForEach-Object {$_.name})
$warningStages=@($script:RestoreStages | Where-Object {$_.status -eq 'WARNING'} | ForEach-Object {$_.name})
$result=if($script:State -and $script:State.status -eq 'RESTORE_PARTIAL'){'RESTORE_INCOMPLETE'}
elseif($failed.Count -gt 0){'RESTORE_INCOMPLETE'}
elseif($warningStages.Count -gt 0){'ACCESS_RESTORED_WITH_WARNINGS'}
elseif($script:State){'ACCESS_RESTORED'}
else{'FAILED'}
$auth=(Get-CombinedRestoreCheck @('REMOVE_AUTH_FIREWALL','RESTORE_KDC_PROXY','RESTORE_NETLOGON'))
$summary="EmergencyRestore v$ScriptVersion | $result | Scope=$(if($script:State){[string]$script:State.scope}else{'UNKNOWN'}) | StateLoaded=$(Get-CombinedRestoreCheck @('LOAD_STATE','VALIDATE_ACTIVE_STATE')) | Rights=$(Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'RESTORE_USER_RIGHTS')) | Cache=$(Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'RESTORE_CACHED_DOMAIN_LOGON')) | Auth=$auth"
if($failed.Count -gt 0){$summary += " | Failed=$($failed -join ',')"}
if($warningStages.Count -gt 0){$summary += " | Warn=$($warningStages -join ',')"}
$summary += " | State=$StatePath"
[Console]::Out.WriteLine($summary)
}
function Test-SecurePathAcl {
param([string]$Path)
$acl=Get-Acl -LiteralPath $Path -ErrorAction Stop
$ownerSid=try{([System.Security.Principal.NTAccount]$acl.Owner).Translate([System.Security.Principal.SecurityIdentifier]).Value}catch{[string]$acl.Owner}
$allowed=@('S-1-5-18','S-1-5-32-544');$unexpected=@()
foreach($rule in @($acl.Access)){$sid=try{$rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value}catch{[string]$rule.IdentityReference};if($rule.AccessControlType -ne 'Allow' -or $sid -notin $allowed){$unexpected += $sid}}
[pscustomobject]@{Pass=($acl.AreAccessRulesProtected -and $ownerSid -eq 'S-1-5-18' -and $unexpected.Count -eq 0);Owner=$ownerSid;Protected=[bool]$acl.AreAccessRulesProtected;Unexpected=($unexpected -join ';')}
}
function Test-IsSystemOrAdministrator {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
if ($identity.User.Value -eq 'S-1-5-18') { return $true }
return [System.Security.Principal.WindowsPrincipal]::new($identity).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Initialize-LsaNativeType {
if('EmergencySecurity.RestoreNativeV13' -as [type]){return}
$source=@'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace EmergencySecurity
{
public static class RestoreNativeV13
{
[StructLayout(LayoutKind.Sequential)] private struct LSA_OBJECT_ATTRIBUTES{public int Length;public IntPtr RootDirectory;public IntPtr ObjectName;public uint Attributes;public IntPtr SecurityDescriptor;public IntPtr SecurityQualityOfService;}
[StructLayout(LayoutKind.Sequential)] private struct LSA_UNICODE_STRING{public ushort Length;public ushort MaximumLength;public IntPtr Buffer;}
[DllImport("advapi32.dll")] private static extern uint LsaOpenPolicy(IntPtr SystemName,ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,uint DesiredAccess,out IntPtr PolicyHandle);
[DllImport("advapi32.dll")] private static extern uint LsaRemoveAccountRights(IntPtr PolicyHandle,IntPtr AccountSid,bool AllRights,LSA_UNICODE_STRING[] UserRights,uint CountOfRights);
[DllImport("advapi32.dll")] private static extern uint LsaEnumerateAccountRights(IntPtr PolicyHandle,IntPtr AccountSid,out IntPtr UserRights,out uint CountOfRights);
[DllImport("advapi32.dll")] private static extern uint LsaNtStatusToWinError(uint Status);
[DllImport("advapi32.dll")] private static extern uint LsaClose(IntPtr PolicyHandle);
[DllImport("advapi32.dll")] private static extern uint LsaFreeMemory(IntPtr Buffer);
private const uint POLICY_LOOKUP_NAMES=0x00000800;private const uint POLICY_CREATE_ACCOUNT=0x00000010;private const uint STATUS_OBJECT_NAME_NOT_FOUND=0xC0000034;
private static IntPtr SidToPtr(string sidValue){SecurityIdentifier sid=new SecurityIdentifier(sidValue);byte[] b=new byte[sid.BinaryLength];sid.GetBinaryForm(b,0);IntPtr p=Marshal.AllocHGlobal(b.Length);Marshal.Copy(b,0,p,b.Length);return p;}
public static void RemoveRight(string sidValue,string right){LSA_OBJECT_ATTRIBUTES loa=new LSA_OBJECT_ATTRIBUTES();loa.Length=Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));IntPtr policy=IntPtr.Zero,sid=IntPtr.Zero,text=IntPtr.Zero;try{uint status=LsaOpenPolicy(IntPtr.Zero,ref loa,POLICY_LOOKUP_NAMES|POLICY_CREATE_ACCOUNT,out policy);if(status!=0)throw new Win32Exception((int)LsaNtStatusToWinError(status));sid=SidToPtr(sidValue);text=Marshal.StringToHGlobalUni(right);LSA_UNICODE_STRING item=new LSA_UNICODE_STRING{Length=(ushort)(right.Length*2),MaximumLength=(ushort)((right.Length+1)*2),Buffer=text};status=LsaRemoveAccountRights(policy,sid,false,new LSA_UNICODE_STRING[]{item},1);if(status!=0&&status!=STATUS_OBJECT_NAME_NOT_FOUND)throw new Win32Exception((int)LsaNtStatusToWinError(status));}finally{if(text!=IntPtr.Zero)Marshal.FreeHGlobal(text);if(sid!=IntPtr.Zero)Marshal.FreeHGlobal(sid);if(policy!=IntPtr.Zero)LsaClose(policy);}}
public static string[] GetRights(string sidValue){LSA_OBJECT_ATTRIBUTES loa=new LSA_OBJECT_ATTRIBUTES();loa.Length=Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));IntPtr policy=IntPtr.Zero,sid=IntPtr.Zero,rights=IntPtr.Zero;try{uint status=LsaOpenPolicy(IntPtr.Zero,ref loa,POLICY_LOOKUP_NAMES,out policy);if(status!=0)throw new Win32Exception((int)LsaNtStatusToWinError(status));sid=SidToPtr(sidValue);uint count;status=LsaEnumerateAccountRights(policy,sid,out rights,out count);if(status==STATUS_OBJECT_NAME_NOT_FOUND)return new string[0];if(status!=0)throw new Win32Exception((int)LsaNtStatusToWinError(status));List<string> list=new List<string>();int size=Marshal.SizeOf(typeof(LSA_UNICODE_STRING));for(int i=0;i<count;i++){LSA_UNICODE_STRING item=(LSA_UNICODE_STRING)Marshal.PtrToStructure(IntPtr.Add(rights,i*size),typeof(LSA_UNICODE_STRING));list.Add(Marshal.PtrToStringUni(item.Buffer,item.Length/2));}return list.ToArray();}finally{if(rights!=IntPtr.Zero)LsaFreeMemory(rights);if(sid!=IntPtr.Zero)Marshal.FreeHGlobal(sid);if(policy!=IntPtr.Zero)LsaClose(policy);}}
}
}
'@
Add-Type -TypeDefinition $source -Language CSharp -ErrorAction Stop | Out-Null
}
function Get-CachedLogonCurrent {
$path='HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon';try{return [pscustomobject]@{exists=$true;value=[string](Get-ItemProperty -Path $path -Name CachedLogonsCount -ErrorAction Stop).CachedLogonsCount}}catch{return [pscustomobject]@{exists=$false;value=$null}}
}
function Restore-CachedLogonState {
param($Original)
$path='HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
if([bool]$Original.existed){$propertyType=if([string]$Original.kind -eq 'DWord'){'DWord'}elseif([string]$Original.kind -eq 'QWord'){'QWord'}elseif([string]$Original.kind -eq 'ExpandString'){'ExpandString'}elseif([string]$Original.kind -eq 'MultiString'){'MultiString'}else{'String'};New-ItemProperty -Path $path -Name CachedLogonsCount -Value $Original.value -PropertyType $propertyType -Force | Out-Null}else{Remove-ItemProperty -Path $path -Name CachedLogonsCount -ErrorAction SilentlyContinue}
$current=Get-CachedLogonCurrent;if([bool]$Original.existed){if(-not $current.exists -or [string]$current.value -ne [string]$Original.value){throw 'CachedLogonsCount restore verification failed.'}}elseif($current.exists){throw 'CachedLogonsCount should be absent after restore.'}
}
function Restore-KdcProxyState {
param($Original)
$rootPath = [string]$Original.rootPath
if ([string]::IsNullOrWhiteSpace($rootPath)) { return }
$mappingPath = Join-Path $rootPath 'KdcProxyServer'
if ([bool]$Original.enabledValueExisted) {
if (-not (Test-Path $rootPath)) { $null = New-Item -Path $rootPath -Force }
New-ItemProperty -Path $rootPath -Name 'KdcProxyServer_Enabled' -Value $Original.enabledValue -PropertyType DWord -Force | Out-Null
}
else {
Remove-ItemProperty -Path $rootPath -Name 'KdcProxyServer_Enabled' -ErrorAction SilentlyContinue
}
if (Test-Path $mappingPath) { Remove-Item -Path $mappingPath -Recurse -Force -ErrorAction SilentlyContinue }
if ([bool]$Original.mappingPathExisted) {
$null = New-Item -Path $mappingPath -Force
foreach ($mapping in @($Original.mappings)) {
New-ItemProperty -Path $mappingPath -Name ([string]$mapping.name) -Value ([string]$mapping.value) -PropertyType String -Force | Out-Null
}
}
}
function Restore-NetlogonState {
param($Original)
$startupType = switch ([string]$Original.startMode) {
'Auto' { 'Automatic' }
'Automatic' { 'Automatic' }
'Manual' { 'Manual' }
'Disabled' { 'Disabled' }
default { 'Manual' }
}
Set-Service -Name Netlogon -StartupType $startupType -ErrorAction Stop
if ([string]$Original.state -eq 'Running' -and $startupType -ne 'Disabled') {
Start-Service -Name Netlogon -ErrorAction Stop
}
elseif ([string]$Original.state -ne 'Running') {
Stop-Service -Name Netlogon -Force -ErrorAction SilentlyContinue
}
}
function Cancel-PendingEmergencyDisruption {
foreach($name in $DisruptionTaskNames){
try{& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /End /TN $name 2>$null | Out-Null}catch{}
try{& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Delete /TN $name /F 2>$null | Out-Null}catch{}
}
foreach($name in @('EmergencyLockdown-PostBootVerify-v140','EmergencyLockdown-PostBootVerify-v135','EmergencyLockdown-PostBootVerify-v134','EmergencyLockdown-PostBootVerify-v133','EmergencyLockdown-PostBootVerify-v132','EmergencyLockdown-PostBootVerify-v131','EmergencyLockdown-PostBootVerify-v13')){
try{& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Delete /TN $name /F 2>$null | Out-Null}catch{}
}
try{& (Join-Path $env:SystemRoot 'System32\shutdown.exe') /a 2>$null | Out-Null}catch{}
try{Remove-Item -LiteralPath $DisruptionWorkerPath -Force -ErrorAction SilentlyContinue}catch{}
}
function Install-PostBootVerifier {
param([ValidateSet('LOCKDOWN','RESTORE')][string]$Operation)
Set-SecureDirectoryAcl -Path $BaseDirectory
[System.IO.File]::WriteAllBytes($PostBootVerifierPath,[Convert]::FromBase64String($PostBootVerifierBase64));Set-SecureFileAcl $PostBootVerifierPath
$aclCheck=Test-SecurePathAcl -Path $PostBootVerifierPath
if(-not $aclCheck.Pass){throw "Post-boot verifier ACL verification failed. Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected);Unexpected=$($aclCheck.Unexpected)"}
$powerShellExe=Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe';$taskCommand='"{0}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{1}"' -f $powerShellExe,$PostBootVerifierPath
& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Create /TN $PostBootTaskName /SC ONSTART /RU SYSTEM /RL HIGHEST /TR $taskCommand /F | Out-Null;if($LASTEXITCODE -ne 0){throw "schtasks create returned $LASTEXITCODE"}
& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Query /TN $PostBootTaskName | Out-Null;if($LASTEXITCODE -ne 0){throw 'Post-boot task query failed.'}
$pending=[pscustomobject][ordered]@{operation=$Operation;status='PENDING';overallStatus='PENDING';scheduledUtc=Get-UtcTimestamp;verifiedUtc=$null;rebootVerified=$false;passedCheckCount=0;failedCheckCount=0;failedChecks='';taskName=$PostBootTaskName;scriptPath=$PostBootVerifierPath;checks=@()};if($script:State.PSObject.Properties.Name -contains 'postBootVerification'){$script:State.postBootVerification=$pending}else{$script:State|Add-Member -NotePropertyName postBootVerification -NotePropertyValue $pending};Write-State
}
function Cancel-PostBootVerifier {param([string]$Reason);try{& (Join-Path $env:SystemRoot 'System32\schtasks.exe') /Delete /TN $PostBootTaskName /F | Out-Null}catch{};if($script:State -and $script:State.PSObject.Properties.Name -contains 'postBootVerification'){$script:State.postBootVerification.status='CANCELED';$script:State.postBootVerification.overallStatus='CANCELED'};Write-State}
function Schedule-Reboot {
$os=Get-CimInstance Win32_OperatingSystem -ErrorAction Stop;$boot=([datetime]$os.LastBootUpTime).ToUniversalTime().ToString('o')
if($script:State.PSObject.Properties.Name -contains 'restoreBootTimeBefore'){$script:State.restoreBootTimeBefore=$boot}else{$script:State | Add-Member -NotePropertyName restoreBootTimeBefore -NotePropertyValue $boot}
$arguments=@('/r','/f','/t',[string]$RebootDelaySeconds,'/c','"Emergency access restore completed: mandatory restart"');$process=Start-Process -FilePath "$env:SystemRoot\System32\shutdown.exe" -ArgumentList $arguments -Wait -PassThru -WindowStyle Hidden;if($process.ExitCode -ne 0){throw "shutdown.exe returned exit code $($process.ExitCode)."}
if($script:State.PSObject.Properties.Name -contains 'restoreRebootScheduled'){$script:State.restoreRebootScheduled=$true}else{$script:State | Add-Member -NotePropertyName restoreRebootScheduled -NotePropertyValue $true}
if($script:State.PSObject.Properties.Name -contains 'restoreRebootScheduledUtc'){$script:State.restoreRebootScheduledUtc=Get-UtcTimestamp}else{$script:State | Add-Member -NotePropertyName restoreRebootScheduledUtc -NotePropertyValue (Get-UtcTimestamp)}
}
try {
$start=[DateTime]::UtcNow
try{Set-SecureDirectoryAcl -Path $BaseDirectory;$aclCheck=Test-SecurePathAcl -Path $BaseDirectory;if(-not $aclCheck.Pass){throw "ACL verification failed. Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected);Unexpected=$($aclCheck.Unexpected)"};Add-RestoreStage -Name 'SECURE_STATE_STORAGE' -Status 'SUCCESS' -Started $start -Expected 'Owner SYSTEM; inheritance disabled; only SYSTEM and Administrators' -Actual "Owner=$($aclCheck.Owner);Protected=$($aclCheck.Protected)"}catch{Add-RestoreStage -Name 'SECURE_STATE_STORAGE' -Status 'FAILED' -Started $start -ErrorCode 'STATE_STORAGE_ACL_FAILED' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 10}
$start=[DateTime]::UtcNow
try{Cancel-PendingEmergencyDisruption;Add-RestoreStage -Name 'CANCEL_PENDING_DISRUPTION' -Status 'SUCCESS' -Started $start -Expected 'No pending lockdown worker or shutdown request' -Actual 'Canceled/absent'}catch{Add-RestoreStage -Name 'CANCEL_PENDING_DISRUPTION' -Status 'WARNING' -Started $start -ErrorCode 'PENDING_DISRUPTION_CANCEL_FAILED' -ErrorMessage $_.Exception.Message}
$start=[DateTime]::UtcNow
if(-not (Test-IsSystemOrAdministrator)){Add-RestoreStage -Name 'LOAD_STATE' -Status 'FAILED' -Started $start -ErrorCode 'ELEVATION_REQUIRED' -ErrorMessage 'Restore must run as SYSTEM or elevated administrator.';Write-Remarks;exit 11}
Initialize-LsaNativeType
if(-not (Test-Path -LiteralPath $StatePath)){Add-RestoreStage -Name 'LOAD_STATE' -Status 'FAILED' -Started $start -ErrorCode 'STATE_FILE_NOT_FOUND' -ErrorMessage $StatePath;Write-Remarks;exit 12}
try{$script:State=Get-Content -LiteralPath $StatePath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop;Add-RestoreStage -Name 'LOAD_STATE' -Status 'SUCCESS' -Started $start -Expected 'Readable state JSON' -Actual ([string]$script:State.status)}catch{Add-RestoreStage -Name 'LOAD_STATE' -Status 'FAILED' -Started $start -ErrorCode 'STATE_FILE_UNREADABLE' -ErrorMessage $_.Exception.Message;Write-Remarks;exit 13}
$start=[DateTime]::UtcNow
if([string]$script:State.status -notin @('APPLYING','ACTIVE_DISRUPTION_PENDING','ACTIVE_PENDING_REBOOT','ACTIVE','ACTIVE_VERIFIED','ACTIVE_VERIFIED_WITH_WARNINGS','LOCKDOWN_PARTIAL','RESTORING','RESTORED_PENDING_REBOOT','RESTORE_PARTIAL','UNHANDLED_FAILURE')){Add-RestoreStage -Name 'VALIDATE_ACTIVE_STATE' -Status 'FAILED' -Started $start -ErrorCode 'NO_ACTIVE_LOCKDOWN' -ErrorMessage "State status is $($script:State.status).";Write-Remarks;exit 14}
Add-RestoreStage -Name 'VALIDATE_ACTIVE_STATE' -Status 'SUCCESS' -Started $start -Actual ([string]$script:State.status)
if($script:State.PSObject.Properties.Name -contains 'schemaVersion'){$script:State.schemaVersion=3}else{$script:State | Add-Member -NotePropertyName schemaVersion -NotePropertyValue 3}
if($script:State.PSObject.Properties.Name -contains 'postBootVerification' -and [string]$script:State.postBootVerification.operation -eq 'LOCKDOWN'){if($script:State.PSObject.Properties.Name -contains 'lockdownPostBootVerification'){$script:State.lockdownPostBootVerification=$script:State.postBootVerification}else{$script:State | Add-Member -NotePropertyName lockdownPostBootVerification -NotePropertyValue $script:State.postBootVerification}}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownOperation')){$script:State | Add-Member -NotePropertyName lockdownOperation -NotePropertyValue ([string]$script:State.operation)}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownScriptVersion')){$script:State | Add-Member -NotePropertyName lockdownScriptVersion -NotePropertyValue ([string]$script:State.scriptVersion)}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownStartedUtc')){$script:State | Add-Member -NotePropertyName lockdownStartedUtc -NotePropertyValue ([string]$script:State.startedUtc)}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownCompletedUtc')){$script:State | Add-Member -NotePropertyName lockdownCompletedUtc -NotePropertyValue ([string]$script:State.completedUtc)}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownWarnings')){$script:State | Add-Member -NotePropertyName lockdownWarnings -NotePropertyValue @($script:State.warnings)}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownErrors')){$script:State | Add-Member -NotePropertyName lockdownErrors -NotePropertyValue @($script:State.errors)}
if(-not ($script:State.PSObject.Properties.Name -contains 'lockdownActions')){$script:State | Add-Member -NotePropertyName lockdownActions -NotePropertyValue $script:State.actions}
$restoreActions=[pscustomobject][ordered]@{rights='NOT_STARTED';cachedDomainLogon='NOT_STARTED';authFirewall='NOT_STARTED';kdcProxy='NOT_STARTED';netlogon='NOT_STARTED';postBootVerifier='NOT_STARTED';rebootRequest='NOT_STARTED'}
if($script:State.PSObject.Properties.Name -contains 'restoreActions'){$script:State.restoreActions=$restoreActions}else{$script:State | Add-Member -NotePropertyName restoreActions -NotePropertyValue $restoreActions}
$restoreStart=Get-UtcTimestamp
$script:State.operation='RESTORE';$script:State.scriptVersion=$ScriptVersion;$script:State.startedUtc=$restoreStart;$script:State.completedUtc=$null;$script:State.warnings=@();$script:State.errors=@()
if($script:State.PSObject.Properties.Name -contains 'restoreStartedUtc'){$script:State.restoreStartedUtc=$restoreStart}else{$script:State | Add-Member -NotePropertyName restoreStartedUtc -NotePropertyValue $restoreStart};$script:State.status='RESTORING';Write-State
$start=[DateTime]::UtcNow;$removedRights=0;$rightFailures=@()
try{foreach($record in @($script:State.originalState.userRights)){foreach($right in @($record.rightsAdded)){try{[EmergencySecurity.RestoreNativeV13]::RemoveRight([string]$record.sid,[string]$right);$after=@([EmergencySecurity.RestoreNativeV13]::GetRights([string]$record.sid));if($right -in $after){throw 'Right is still present after removal.'};$removedRights++}catch{$rightFailures += "$($record.accountName):${right}:$($_.Exception.Message)"}}};if($rightFailures.Count -gt 0){Add-RestoreStage -Name 'RESTORE_USER_RIGHTS' -Status 'WARNING' -Started $start -AffectedCount $removedRights -ErrorCode 'ONE_OR_MORE_RIGHTS_NOT_REMOVED' -ErrorMessage ($rightFailures -join ' | ') -Expected 'All lockdown-added rights absent' -Actual "$removedRights removed"}else{Add-RestoreStage -Name 'RESTORE_USER_RIGHTS' -Status 'SUCCESS' -Started $start -AffectedCount $removedRights -Expected 'All lockdown-added rights absent' -Actual "$removedRights removed and verified"}}
catch{Add-RestoreStage -Name 'RESTORE_USER_RIGHTS' -Status 'FAILED' -Started $start -ErrorCode 'RIGHTS_RESTORE_FAILED' -ErrorMessage $_.Exception.Message}
$start=[DateTime]::UtcNow;try{Restore-CachedLogonState -Original $script:State.originalState.cachedLogonsCount;Add-RestoreStage -Name 'RESTORE_CACHED_DOMAIN_LOGON' -Status 'SUCCESS' -Started $start -AffectedCount 1 -Expected 'Original value/existence' -Actual 'Verified'}catch{Add-RestoreStage -Name 'RESTORE_CACHED_DOMAIN_LOGON' -Status 'FAILED' -Started $start -ErrorCode 'CACHED_LOGON_RESTORE_FAILED' -ErrorMessage $_.Exception.Message}
$allScope=[string]$script:State.scope -in @('ALL_USERS','ALL_USERS_SAVE_LAPS');$partOfDomain=$false
try{$partOfDomain=[bool]$script:State.actions.domainContext.partOfDomain}catch{}
if($allScope -and $partOfDomain){
$start=[DateTime]::UtcNow;try{$definitions=@($script:State.actions.authFirewall.ruleDefinitions);foreach($definition in $definitions){Remove-NetFirewallRule -Name ([string]$definition.name) -ErrorAction SilentlyContinue};$remaining=@($definitions | Where-Object {Get-NetFirewallRule -Name ([string]$_.name) -ErrorAction SilentlyContinue});if($remaining.Count -gt 0){throw 'One or more emergency firewall rules remain.'};Add-RestoreStage -Name 'REMOVE_AUTH_FIREWALL' -Status 'SUCCESS' -Started $start -AffectedCount $definitions.Count -Expected 'Emergency rules absent' -Actual 'Verified'}catch{Add-RestoreStage -Name 'REMOVE_AUTH_FIREWALL' -Status 'FAILED' -Started $start -ErrorCode 'AUTH_FIREWALL_RESTORE_FAILED' -ErrorMessage $_.Exception.Message}
$start=[DateTime]::UtcNow;try{Restore-KdcProxyState -Original $script:State.originalState.kdcProxy;Add-RestoreStage -Name 'RESTORE_KDC_PROXY' -Status 'SUCCESS' -Started $start -Expected 'Original KDC proxy state' -Actual 'Applied; post-boot verifier will recheck'}catch{Add-RestoreStage -Name 'RESTORE_KDC_PROXY' -Status 'WARNING' -Started $start -ErrorCode 'KDC_PROXY_RESTORE_FAILED' -ErrorMessage $_.Exception.Message}
$start=[DateTime]::UtcNow;try{Restore-NetlogonState -Original $script:State.originalState.netlogon;$net=Get-CimInstance Win32_Service -Filter "Name='Netlogon'" -ErrorAction Stop;Add-RestoreStage -Name 'RESTORE_NETLOGON' -Status 'SUCCESS' -Started $start -AffectedCount 1 -Expected "$($script:State.originalState.netlogon.state)/$($script:State.originalState.netlogon.startMode)" -Actual "$($net.State)/$($net.StartMode)"}catch{Add-RestoreStage -Name 'RESTORE_NETLOGON' -Status 'FAILED' -Started $start -ErrorCode 'NETLOGON_RESTORE_FAILED' -ErrorMessage $_.Exception.Message}
}else{Add-RestoreStage -Name 'REMOVE_AUTH_FIREWALL' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow);Add-RestoreStage -Name 'RESTORE_KDC_PROXY' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow);Add-RestoreStage -Name 'RESTORE_NETLOGON' -Status 'NOT_APPLICABLE' -Started ([DateTime]::UtcNow)}
$script:State | Add-Member -NotePropertyName restoreBootTimeBefore -NotePropertyValue (([datetime](Get-CimInstance Win32_OperatingSystem -ErrorAction Stop).LastBootUpTime).ToUniversalTime().ToString('o')) -Force;Write-State
$changesRestored=@($script:RestoreStages | Where-Object {$_.name -in @('RESTORE_USER_RIGHTS','RESTORE_CACHED_DOMAIN_LOGON','REMOVE_AUTH_FIREWALL','RESTORE_KDC_PROXY','RESTORE_NETLOGON') -and $_.status -in @('SUCCESS','WARNING')}).Count -gt 0
if($changesRestored){$start=[DateTime]::UtcNow;try{Install-PostBootVerifier -Operation 'RESTORE';Add-RestoreStage -Name 'INSTALL_POSTBOOT_VERIFIER' -Status 'SUCCESS' -Started $start -AffectedCount 1 -Expected 'Startup task query succeeds' -Actual $PostBootTaskName}catch{Add-RestoreStage -Name 'INSTALL_POSTBOOT_VERIFIER' -Status 'WARNING' -Started $start -ErrorCode 'POSTBOOT_VERIFIER_INSTALL_FAILED' -ErrorMessage $_.Exception.Message}
$start=[DateTime]::UtcNow;$script:State.status='RESTORED_PENDING_REBOOT';Add-RestoreStage -Name 'PREPARE_REBOOT_CHECKPOINT' -Status 'SUCCESS' -Started $start -Message 'Restore state was persisted before reboot scheduling.';Write-State
$start=[DateTime]::UtcNow;try{Schedule-Reboot;try{Add-RestoreStage -Name 'SCHEDULE_REBOOT' -Status 'SUCCESS' -Started $start -AffectedCount 1 -Expected 'shutdown.exe accepted restart request' -Actual "ExitCode=0;Delay=$RebootDelaySeconds seconds" -Verification 'PENDING'}catch{& (Join-Path $env:SystemRoot 'System32\shutdown.exe') /a 2>$null | Out-Null;throw "Restore reboot request was accepted but could not be durably recorded; the request was aborted. $($_.Exception.Message)"}}catch{try{Cancel-PostBootVerifier -Reason 'Restore reboot scheduling or durable recording failed.'}catch{};Add-RestoreStage -Name 'SCHEDULE_REBOOT' -Status 'FAILED' -Started $start -ErrorCode 'REBOOT_SCHEDULE_FAILED' -ErrorMessage $_.Exception.Message}}
else{Add-RestoreStage -Name 'INSTALL_POSTBOOT_VERIFIER' -Status 'SKIPPED' -Started ([DateTime]::UtcNow);Add-RestoreStage -Name 'PREPARE_REBOOT_CHECKPOINT' -Status 'SKIPPED' -Started ([DateTime]::UtcNow);Add-RestoreStage -Name 'SCHEDULE_REBOOT' -Status 'SKIPPED' -Started ([DateTime]::UtcNow) -Message 'No restorable security change was completed.'}
$start=[DateTime]::UtcNow;$hardFailures=@($script:RestoreStages | Where-Object {$_.status -eq 'FAILED'}).Count;$warningCount=@($script:RestoreStages | Where-Object {$_.status -eq 'WARNING'}).Count
$script:State.status=if((Get-RestoreStageStatus 'SCHEDULE_REBOOT') -eq 'SUCCESS' -and $hardFailures -eq 0){'RESTORED_PENDING_REBOOT'}else{'RESTORE_PARTIAL'}
$restoreCompleted=Get-UtcTimestamp
if($script:State.PSObject.Properties.Name -contains 'restoreCompletedUtc'){$script:State.restoreCompletedUtc=$restoreCompleted}else{$script:State | Add-Member -NotePropertyName restoreCompletedUtc -NotePropertyValue $restoreCompleted}
$script:State.completedUtc=$restoreCompleted
Add-RestoreStage -Name 'FINALIZE_RESTORE_STATE' -Status $(if($script:State.status -eq 'RESTORED_PENDING_REBOOT'){'SUCCESS'}else{'WARNING'}) -Started $start -ErrorCode $(if($script:State.status -eq 'RESTORED_PENDING_REBOOT'){$null}else{'RESTORE_PARTIAL'}) -ErrorMessage $(if($script:State.status -eq 'RESTORED_PENDING_REBOOT'){$null}else{'Restore completed with warnings or failures. Review the state file.'})
$script:State.restoreActions.rights=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'RESTORE_USER_RIGHTS')
$script:State.restoreActions.cachedDomainLogon=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'RESTORE_CACHED_DOMAIN_LOGON')
$script:State.restoreActions.authFirewall=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'REMOVE_AUTH_FIREWALL')
$script:State.restoreActions.kdcProxy=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'RESTORE_KDC_PROXY')
$script:State.restoreActions.netlogon=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'RESTORE_NETLOGON')
$script:State.restoreActions.postBootVerifier=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'INSTALL_POSTBOOT_VERIFIER')
$script:State.restoreActions.rebootRequest=Convert-RestoreStatusForRemarks (Get-RestoreStageStatus 'SCHEDULE_REBOOT')
Write-State
Write-Remarks;exit $(if($script:State.status -eq 'RESTORED_PENDING_REBOOT'){0}else{2})
}catch{
try{if($null -ne $script:State){$script:State.status='RESTORE_PARTIAL';$script:Errors += [pscustomobject]@{code='UNHANDLED_EXCEPTION';message=$_.Exception.Message;stage='UNHANDLED';timestampUtc=Get-UtcTimestamp};Write-State};Write-Remarks}catch{[Console]::Out.WriteLine((([ordered]@{script='EmergencyRestore';version=$ScriptVersion;operation='RESTORE';result='FAILED';failed='UNHANDLED_EXCEPTION;REMARKS_GENERATION_FAILED';message=$_.Exception.Message}) | ConvertTo-Json -Compress))};exit 99
}Tam Transkript
Değerlendirme
'ta yorum yapın ya da tartışın

