Deneme Altında Olan Betiklerim 2026

Programlama ve Script dilleri konusunda bilgi paylaşım alanıdır.
Cevapla
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

Deneme Altında Olan Betiklerim 2026

Mesaj gönderen TRWE_2012 »

Merhabalar

Bu açılan forum konusunda, denenmesi gerçek sistem'de riskli betiklerin sanal işletim sisteminde denen ve olumlu sonuçlanan betiklere yer verilecektir.Eğer betik başarılı ise ve canlı sistem'de sistem'e sorun verdirmemişse, buradan nihai kararlı sürümü yayınlacak...(forum'a alt mesaj şeklinde yeniden düzenlenecek)

İlk betiğimiz : GuncellemeKur.ps1

KOD İÇERİK AÇIKLAMASI :

Kod: Tümünü seç

# =====================================================================
# Otomatik Windows Update (.msu ve .cab) Kurulum Betiği
# Windows 11 / 10 / 8.1 / 7 Uyumlu
# =====================================================================

# 1. Yönetici Yetkisi Kontrolü
 $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    Write-Warning "HATA: Bu betik güncelleme kuracağı için YÖNETİCİ olarak çalıştırılmalıdır!"
    Write-Host "Lütfen PowerShell'i Yönetici olarak açıp tekrar deneyin." -ForegroundColor Cyan
    Read-Host "Çıkmak için Enter'a basın"
    exit
}

# 2. Hedef Dizini Belirleme (Betik ile aynı klasör)
 $TargetDir = $PSScriptRoot
if (-not $TargetDir) { $TargetDir = Get-Location }

# 3. Dosyaları Alma ve Sıralama (.cab ve .msu)
 $UpdateFiles = @()
 $UpdateFiles += Get-ChildItem -Path $TargetDir -Filter *.msu -File
 $UpdateFiles += Get-ChildItem -Path $TargetDir -Filter *.cab -File

# İsimlere göre sırala (isteğe bağlı, ama düzenli kurmak için iyi olur)
 $UpdateFiles = $UpdateFiles | Sort-Object Name

if ($UpdateFiles.Count -eq 0) {
    Write-Host "Bu klasörde kurulum için .msu veya .cab dosyası bulunamadı." -ForegroundColor Yellow
    Read-Host "Çıkmak için Enter'a basın"
    exit
}

# 4. Kurulum Döngüsü
Write-Host "======================================================" -ForegroundColor DarkGray
Write-Host " Toplam $($UpdateFiles.Count) güncelleme bulunacak ve sırayla kurulacak." -ForegroundColor Green
Write-Host "======================================================" -ForegroundColor DarkGray

 $SuccessCount = 0
 $FailCount = 0
 $RebootRequired = $false

foreach ($File in $UpdateFiles) {
    Write-Host "`n[$($SuccessCount + $FailCount + 1)/$($UpdateFiles.Count)] Kuruluyor: $($File.Name)" -ForegroundColor Cyan

    $ExitCode = $null

    try {
        if ($File.Extension.ToLower() -eq '.msu') {
            # .msu dosyaları wusa ile kurulur
            $Args = "`"$($File.FullName)`" /quiet /norestart"
            $Process = Start-Process -FilePath "wusa.exe" -ArgumentList $Args -Wait -PassThru -WindowStyle Hidden
            $ExitCode = $Process.ExitCode
        }
        elseif ($File.Extension.ToLower() -eq '.cab') {
            # .cab dosyaları DISM ile kurulur (Görselinizdeki yöntem)
            $Args = "/online /add-package /packagepath:`"$($File.FullName)`" /quiet /norestart"
            $Process = Start-Process -FilePath "dism.exe" -ArgumentList $Args -Wait -PassThru -WindowStyle Hidden
            $ExitCode = $Process.ExitCode
        }

        # Çıkış Kodlarına Göre İşlem
        if ($ExitCode -eq 0) {
            Write-Host "  -> BAŞARILI" -ForegroundColor Green
            $SuccessCount++
        }
        elseif ($ExitCode -eq 3010) {
            Write-Host "  -> BAŞARILI (Yeniden başlatma gerekiyor)" -ForegroundColor Yellow
            $SuccessCount++
            $RebootRequired = $true
        }
        elseif ($ExitCode -eq 2359302) {
            Write-Host "  -> ATLANDI (Bu güncelleme zaten yüklü veya uygunsuz)" -ForegroundColor DarkYellow
            $SuccessCount++ # Zaten yüklü olduğu için hata saymayacağız
        }
        else {
            Write-Host "  -> HATA! Çıkış Kodu: $ExitCode" -ForegroundColor Red
            $FailCount++
        }
    }
    catch {
        Write-Host "  -> KRİTİK HATA: $($_.Exception.Message)" -ForegroundColor Red
        $FailCount++
    }
}

# 5. Sonuç Raporu
Write-Host "`n======================================================" -ForegroundColor DarkGray
Write-Host " KURULUM RAPORU" -ForegroundColor White
Write-Host "======================================================" -ForegroundColor DarkGray
Write-Host " Başarılı / Atlanan : $SuccessCount" -ForegroundColor Green
Write-Host " Başarısız         : $FailCount" -ForegroundColor Red

if ($RebootRequired) {
    Write-Host " DURUM: Güncellemelerin tam etkili olması için sistemi yeniden başlatmanız önerilir." -ForegroundColor Yellow
}

Read-Host "`nİşlemi tamamlamak için Enter'a basın"
ANLATIM :

Bu betik yıllar önce sordum.net web sitesinden aldığım bir ekran görüntüsünden ilham alınarak oluşturuldu/dizayn edildi.
Resim
Yukarıdaki görseldeki .cab dosyası kurulum yöntemi (DISM) ve genel Windows Update mantığı göz önüne alındığında, bir dizindeki tüm .msu ve .cab dosyalarını sırayla, sessizce ve otomatik olarak kuran oldukça güvenli bir PowerShell betiği hazırladım kendimce

Bu betik:

.msu dosyaları için standart wusa.exe aracını kullanır.
.cab dosyaları için görseldeki gibi dism.exe aracını kullanır.
Her dosya kurulumunu bekler (sıralı kurulum).
Hataları ve yeniden başlatma gereksinimlerini (Error Code 3010) yakalar.

Önemli: Güncellemeleri kurmak her zaman Yönetici (Administrator) yetkisi gerektirir. Betik bunu başlangıçta kontrol eder.

Nasıl Kullanılır?

Güncelleme dosyalarınızı (.msu ve .cab) tek bir klasöre kopyalayın (Örn: C:\Guncellemeler).
Not Defteri (Notepad) veya VS Code açın.
Yukarıdaki kodu yapıştırın.
Dosyayı GuncellemeKur.ps1 adıyla kaydedin (Kayıt türünün "Tüm Dosyalar" olduğundan emin olun).
Bu .ps1 dosyasını güncelleme klasörünün içine atın.
Dosyaya sağ tıklayıp "PowerShell ile çalıştır" yapın. (Veya PowerShell'i Yönetici olarak açıp .\GuncellemeKur.ps1 yazın).

Betikteki Önemli Detaylar:

/quiet /norestart:

Güncellemeler arka planda sessizce kurulur ve işletim sistemi kurulum bitince kendi kendine yeniden başlamaz (kullanıcıyı sisteminden etmez).

Error Code 3010:

Windows Update'lerin en meşhur kodudur. Anlamı "Kurulum başarılı ama değişikliklerin aktif olması için restart lazım". Betik bunu hata olarak saymaz, sarı renkle uyarır.

Error Code 2359302:

"Not Applicable". Yani o güncelleme zaten sisteminizde kurulu veya işletim sistemi sürümünüz bu güncellemeyi kabul etmiyor. Betik bunu atlar.

Sıralama (Sort-Object Name):

Önce .cab dosyaları sonra .msu dosyaları karışmasın diye alfabetik sıraya dizer. Bazen güncellemeler birbirinin bağımlısı olduğu için sıralı kurmak hataları önler.

NOT :

Powershell hata verirse aşağıdaki powershell sağ menü betiğini kullanın.(ben şuan kullanıyorum)

Set-AdminPowerShellContextMenu.ps1

Kod: Tümünü seç

# =========================================================
# Script Name : Set-AdminPowerShellContextMenu.ps1
# Purpose     : - Auto-detects the .ps1 ProgID and default "Run with PowerShell" verb
#               - Adds "Run with PowerShell (Admin)" entry that opens an
#                 elevated PowerShell tab INSIDE Windows Terminal, launched
#                 via shell:AppsFolder\<AUMID> + runas (the supported way to
#                 elevate a packaged/MSIX app -- direct .exe paths under
#                 Program Files\WindowsApps are blocked by ACL for
#                 unpackaged callers).
#               - Hides the default entry behind SHIFT (Extended)
# Usage       : .\Set-AdminPowerShellContextMenu.ps1          -> Install
#               .\Set-AdminPowerShellContextMenu.ps1 -Remove  -> Uninstall / Restore
# =========================================================

param(
    [switch]$Remove
)

# --- Require Administrator ---
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Host "This script must be run as Administrator." -ForegroundColor Red
    Read-Host "Press ENTER to exit."
    exit
}

$backupDir = "$env:USERPROFILE\Desktop"
$helperDir = "$env:ProgramData\ContextMenuTools"
$helperScriptPath = "$helperDir\Invoke-AdminPS1InTerminal.ps1"

$adminBasePath    = "Registry::HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\shell\RunWithPowerShellAdmin"
$adminCommandPath = "$adminBasePath\command"

function Convert-ToRegExePath {
    param([string]$PSPathValue)
    return ($PSPathValue -replace '^.*Registry::', '')
}

function Get-Ps1ProgId {
    $ext = Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\.ps1" -ErrorAction SilentlyContinue
    if ($ext -and $ext."(default)") { return $ext."(default)" }
    return $null
}

function Find-DefaultPowerShellVerb {
    param([string]$ProgId)
    $candidatePaths = @(
        "Registry::HKEY_CLASSES_ROOT\$ProgId\Shell",
        "Registry::HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell"
    )
    foreach ($shellRoot in $candidatePaths) {
        if (-not (Test-Path $shellRoot)) { continue }
        $verbs = Get-ChildItem -Path $shellRoot -ErrorAction SilentlyContinue
        foreach ($verb in $verbs) {
            $verbProps = Get-ItemProperty -Path $verb.PSPath -ErrorAction SilentlyContinue
            $cmdKey    = Join-Path $verb.PSPath "command"
            if (-not (Test-Path $cmdKey)) { continue }
            $cmdProps = Get-ItemProperty -Path $cmdKey -ErrorAction SilentlyContinue
            $cmdValue = $cmdProps."(default)"
            if (-not $cmdValue) { continue }
            $hasShield = $verbProps.HasLUAShield
            $isPwsh7   = $cmdValue -match "pwsh\.exe"
            $isPlainPS = $cmdValue -match "\\powershell\.exe"
            if ($isPwsh7) { continue }
            if ($hasShield) { continue }
            if ($verb.PSChildName -eq "RunWithPowerShellAdmin") { continue }
            if ($isPlainPS) {
                return [PSCustomObject]@{
                    PSPath      = $verb.PSPath
                    Name        = $verb.PSChildName
                    Extended    = $verbProps.Extended
                    CommandLine = $cmdValue
                }
            }
        }
    }
    return $null
}

function Get-WindowsTerminalAumid {
    $pkg = Get-AppxPackage -Name "Microsoft.WindowsTerminal*" -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $pkg) { return $null }
    try {
        $manifest = Get-AppxPackageManifest -Package $pkg.PackageFullName -ErrorAction Stop
        $appId = $manifest.Package.Applications.Application.Id
        if (-not $appId) { return $null }
        return "$($pkg.PackageFamilyName)!$appId"
    }
    catch {
        return $null
    }
}

# ---------------------------------------------------------
# Remove / Restore mode
# ---------------------------------------------------------
if ($Remove) {
    if (Test-Path $adminBasePath) {
        Remove-Item -Path $adminBasePath -Recurse -Force
        Write-Host "Removed: Run with PowerShell (Admin) entry." -ForegroundColor Green
    } else {
        Write-Host "Admin entry not found, nothing to remove." -ForegroundColor Yellow
    }

    if (Test-Path $helperScriptPath) {
        Remove-Item -Path $helperScriptPath -Force
        Write-Host "Removed helper script: $helperScriptPath" -ForegroundColor Green
    }
    if ((Test-Path $helperDir) -and ((Get-ChildItem $helperDir -ErrorAction SilentlyContinue).Count -eq 0)) {
        Remove-Item -Path $helperDir -Force
    }

    $progId = Get-Ps1ProgId
    if ($progId) {
        $detected = Find-DefaultPowerShellVerb -ProgId $progId
        if ($detected -and $null -ne $detected.Extended) {
            Remove-ItemProperty -Path $detected.PSPath -Name "Extended" -Force -ErrorAction SilentlyContinue
            Write-Host "Restored visibility for: $($detected.Name)" -ForegroundColor Green
        }
    }

    Read-Host "Press ENTER to exit."
    exit
}

# ---------------------------------------------------------
# Install mode
# ---------------------------------------------------------

Write-Host "Detecting .ps1 file association..." -ForegroundColor Cyan
$progId = Get-Ps1ProgId
if (-not $progId) {
    Write-Host "ERROR: Could not resolve ProgID for .ps1 files. Aborting." -ForegroundColor Red
    Read-Host "Press ENTER to exit."
    exit
}
Write-Host "Detected ProgID: $progId" -ForegroundColor Cyan

$detected = Find-DefaultPowerShellVerb -ProgId $progId
if ($detected) {
    Write-Host "Detected entry to hide: $($detected.Name)" -ForegroundColor Green
} else {
    Write-Host "WARNING: Could not auto-detect the default entry. It will not be hidden." -ForegroundColor Red
}

Write-Host "Detecting Windows Terminal AUMID..." -ForegroundColor Cyan
$aumid = Get-WindowsTerminalAumid
if (-not $aumid) {
    Write-Host "ERROR: Could not resolve Windows Terminal's AUMID. Is it installed?" -ForegroundColor Red
    Read-Host "Press ENTER to exit."
    exit
}
Write-Host "Detected AUMID: $aumid" -ForegroundColor Green

# --- Create the helper script ---
# Direct .exe paths under Program Files\WindowsApps are blocked by ACL for
# unpackaged (regular desktop) callers. Packaged apps must instead be
# activated via shell:AppsFolder\<AUMID>, which IS supported for elevation
# via Start-Process -Verb RunAs. We bake the AUMID in at install time.
#
# NOTE: Uses a LITERAL here-string (@' '@) instead of an expandable one
# (@" "@) so that backticks and $ signs are written to the file exactly
# as typed, with no risk of double-escaping bugs. The AUMID is injected
# afterward via a plain, non-regex string replace.
if (-not (Test-Path $helperDir)) {
    New-Item -Path $helperDir -ItemType Directory -Force | Out-Null
}

$helperTemplate = @'
param(
    [Parameter(Mandatory = $true)]
    [string]$TargetScript
)

$logFile = "$env:TEMP\AdminPS1InTerminal_lastrun.log"
"Run started: $(Get-Date)" | Out-File $logFile -Force
"TargetScript: $TargetScript" | Out-File $logFile -Append

if (-not (Test-Path -LiteralPath $TargetScript)) {
    "ERROR: Target script not found." | Out-File $logFile -Append
    Start-Process powershell.exe -ArgumentList "-NoExit -NoProfile -Command Write-Host 'Target script not found: $TargetScript' -ForegroundColor Red"
    exit
}

$aumid = "__AUMID_PLACEHOLDER__"
$innerArgs = "powershell.exe -NoExit -NoProfile -ExecutionPolicy Bypass -File `"$TargetScript`""

try {
    "Launching shell:AppsFolder\$aumid with args: $innerArgs" | Out-File $logFile -Append
    Start-Process -FilePath "shell:AppsFolder\$aumid" -ArgumentList $innerArgs -Verb RunAs -ErrorAction Stop
    "Launch issued successfully." | Out-File $logFile -Append
}
catch {
    $errMsg = $_.Exception.Message
    "ERROR: $errMsg" | Out-File $logFile -Append
    Start-Process -FilePath "powershell.exe" -ArgumentList "-NoExit -NoProfile -Command Write-Host 'Elevation failed: $errMsg' -ForegroundColor Red"
}
'@

$helperContent = $helperTemplate.Replace('__AUMID_PLACEHOLDER__', $aumid)
Set-Content -Path $helperScriptPath -Value $helperContent -Encoding UTF8 -Force
Write-Host "Helper script created: $helperScriptPath" -ForegroundColor Green

# --- Add / update the always-visible admin entry ---
if (Test-Path $adminBasePath) {
    $existingRegPath = Convert-ToRegExePath -PSPathValue (Get-Item $adminBasePath).PSPath
    reg export "$existingRegPath" "$backupDir\ps1_shell_RunWithPowerShellAdmin_backup.reg" /y | Out-Null
    Write-Host "Existing admin entry backed up." -ForegroundColor Yellow
}

New-Item -Path $adminCommandPath -Force | Out-Null

# The outer process is only a launcher that triggers elevation via the
# helper script; it has no other job, so it runs hidden and exits as soon
# as the helper script hands off to the elevated window. No -NoExit here.
$cmdValue = "powershell.exe -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File `"$helperScriptPath`" -TargetScript `"%1`""
Set-ItemProperty -Path $adminCommandPath -Name "(Default)" -Value $cmdValue

Write-Host "Added: Run with PowerShell (Admin) -> opens elevated PS tab in Windows Terminal via shell:AppsFolder." -ForegroundColor Green

# --- Hide the detected default entry behind SHIFT ---
if ($detected) {
    $regPathForBackup = Convert-ToRegExePath -PSPathValue $detected.PSPath
    $safeName = ($detected.Name -replace '[\\\/:]', '_')
    reg export "$regPathForBackup" "$backupDir\ps1_shell_$safeName`_backup.reg" /y | Out-Null
    Set-ItemProperty -Path $detected.PSPath -Name "Extended" -Value ""
    Write-Host "Default '$($detected.Name)' is now hidden unless SHIFT is held during right-click." -ForegroundColor Green
}

Write-Host ""
Write-Host "Done. Right-click a .ps1 file and choose 'Run with PowerShell (Admin)'." -ForegroundColor Cyan
Write-Host "A single UAC prompt should appear, then Windows Terminal opens with an elevated PowerShell tab." -ForegroundColor Cyan

Read-Host "Press ENTER to exit."
Yönetici modunda bir powershell penrecesi açın .(Ne görüyorsan onu kopyala-yapıştır...)

Sağ Menüye Ekleme :

Kod: Tümünü seç

.\Set-AdminPowerShellContextMenu.ps1          -> Install
Sağ Menüden Kaldırma :

Kod: Tümünü seç

.\Set-AdminPowerShellContextMenu.ps1 -Remove  -> Uninstall / Restore
Şimdi bir powershell betiğin kendisi veya kısayoluna gelin sağ tık yapın...

Ekran görüntüsü :
Resim
Art arda iki terminal penceresi açılacak ,ilk ki hızlıca ACL yetkisi alacak, ikinci terminal betik yürütme işlemini üzerine alacak...

Güle güle kullanın her iki betiği de...(not: kararlı sürüm gelmeden betiği (birinci betiği) kullanmayın)
En son TRWE_2012 tarafından 21 Ağu 2026, 20:39 tarihinde düzenlendi, toplamda 2 kere düzenlendi.
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

GuncellemeKur.ps1 Stable Hale Geldi Ve Adı "Install-WindowsUpdates.ps1" Oldu

Mesaj gönderen TRWE_2012 »

KONU ANLATIMI :
Resim
KOD İÇERİĞİ : ( Install-WindowsUpdates.ps1 )

Kod: Tümünü seç

# =====================================================================
# Automated Windows Update Installer (.msu and .cab)
# Compatible with Windows 11 / 10 / 8.1 / 7
# PowerShell 5.1 and 7.x compatible
# =====================================================================

[CmdletBinding()]
param(
    [switch]$DryRun,
    [string]$LogFolder
)

# 0. Set up logging (optional)
$script:LogFilePath = $null
if ($LogFolder) {
    try {
        if (-not (Test-Path -Path $LogFolder)) {
            New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null
        }
        $LogFileName = "Install-WindowsUpdates_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
        $script:LogFilePath = Join-Path -Path $LogFolder -ChildPath $LogFileName
        New-Item -Path $script:LogFilePath -ItemType File -Force | Out-Null
    }
    catch {
        Write-Warning "ERROR: Could not create log file in '$LogFolder': $($_.Exception.Message)"
        Write-Warning "Continuing without file logging."
        $script:LogFilePath = $null
    }
}

function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ConsoleColor]$Color = 'Gray'
    )
    Write-Host $Message -ForegroundColor $Color
    if ($script:LogFilePath) {
        $Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        Add-Content -Path $script:LogFilePath -Value "[$Timestamp] $Message"
    }
}

if ($DryRun) {
    Write-Log "===== DRY RUN MODE: no packages will actually be installed =====" -Color Magenta
}
if ($script:LogFilePath) {
    Write-Log "Log file: $script:LogFilePath" -Color DarkGray
}

# 1. Administrator privilege check (skipped in DryRun, since nothing is applied)
if (-not $DryRun) {
    $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        Write-Warning "ERROR: This script must be run as ADMINISTRATOR to install updates."
        Write-Host "Please reopen PowerShell as Administrator and try again." -ForegroundColor Cyan
        Write-Host "Tip: use -DryRun to preview the update list without elevation." -ForegroundColor Cyan
        Read-Host "Press ENTER to exit."
        exit
    }
}

# 2. Determine target directory (same folder as the script)
$TargetDir = $PSScriptRoot
if (-not $TargetDir) { $TargetDir = Get-Location }

# 3. Collect and sort update files (.msu and .cab)
$UpdateFiles = @()
$UpdateFiles += Get-ChildItem -Path $TargetDir -Filter *.msu -File
$UpdateFiles += Get-ChildItem -Path $TargetDir -Filter *.cab -File
$UpdateFiles = $UpdateFiles | Sort-Object Name

if ($UpdateFiles.Count -eq 0) {
    Write-Log "No .msu or .cab files found in this folder." -Color Yellow
    Read-Host "Press ENTER to exit."
    exit
}

Write-Log "======================================================" -Color DarkGray
Write-Log " $($UpdateFiles.Count) update file(s) found. Processing in order." -Color Green
Write-Log " NOTE: alphabetical file order does NOT guarantee correct" -Color DarkGray
Write-Log " install order. If a package is a Servicing Stack Update" -Color DarkGray
Write-Log " (SSU), it must be installed before the related Cumulative" -Color DarkGray
Write-Log " Update (LCU). Check file names/KB numbers manually first." -Color DarkGray
Write-Log "======================================================" -Color DarkGray

$SuccessCount = 0
$FailCount = 0
$DryRunCount = 0
$RebootRequired = $false

foreach ($File in $UpdateFiles) {
    $Position = $SuccessCount + $FailCount + $DryRunCount + 1
    Write-Log "`n[$Position/$($UpdateFiles.Count)] $($File.Name)" -Color Cyan

    # DISM handles both .cab and .msu packages directly.
    # NOTE: wusa.exe is intentionally NOT used here. Since Windows 10
    # version 1803, wusa.exe refuses to silently install cumulative
    # update .msu packages downloaded from the Microsoft Update
    # Catalog (it fails with exit code 87). DISM /Add-Package works
    # for both file types and does not have this restriction.
    $Args = "/online /add-package /packagepath:`"$($File.FullName)`" /quiet /norestart"

    if ($DryRun) {
        Write-Log "  -> DRY RUN: would run: dism.exe $Args" -Color Magenta
        $DryRunCount++
        continue
    }

    $ExitCode = $null

    try {
        $Process = Start-Process -FilePath "dism.exe" -ArgumentList $Args -Wait -PassThru -WindowStyle Hidden
        $ExitCode = $Process.ExitCode

        switch ($ExitCode) {
            0 {
                Write-Log "  -> SUCCESS" -Color Green
                $SuccessCount++
            }
            3010 {
                Write-Log "  -> SUCCESS (reboot required)" -Color Yellow
                $SuccessCount++
                $RebootRequired = $true
            }
            2359302 {
                Write-Log "  -> SKIPPED (already installed or not applicable)" -Color DarkYellow
                $SuccessCount++
            }
            -2146498530 {
                Write-Log "  -> SKIPPED (package not applicable to this OS build)" -Color DarkYellow
                $SuccessCount++
            }
            default {
                Write-Log "  -> ERROR. Exit code: $ExitCode" -Color Red
                $FailCount++
            }
        }
    }
    catch {
        Write-Log "  -> CRITICAL ERROR: $($_.Exception.Message)" -Color Red
        $FailCount++
    }
}

# 4. Summary report
Write-Log "`n======================================================" -Color DarkGray
Write-Log " INSTALLATION REPORT" -Color White
Write-Log "======================================================" -Color DarkGray

if ($DryRun) {
    Write-Log " Files that would be processed : $DryRunCount" -Color Magenta
    Write-Log " STATUS: dry run only, no changes were made to the system." -Color Magenta
}
else {
    Write-Log " Success / Skipped : $SuccessCount" -Color Green
    Write-Log " Failed            : $FailCount" -Color Red
    if ($RebootRequired) {
        Write-Log " STATUS: A system reboot is recommended for updates to take full effect." -Color Yellow
    }
}

if ($script:LogFilePath) {
    Write-Log "Full log saved to: $script:LogFilePath" -Color DarkGray
}

Read-Host "`nPress ENTER to exit."
Güle güle kullanın.
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

Manage-LocalGP.ps1 Geliştirmeye Başlandı...

Mesaj gönderen TRWE_2012 »

Merhabalar

Yıllar önceydi..Sordum.net'de bir makale okurken şu ekran görüntüsü dikkatimi cezbetmişti...
Resim
Resim
Sanırım o zamanlar baskın Windows İşletim Sistemi : Windows7 idi...

İşte bende bunu konu alan bir .ps1 betiği tasarladım. (Ön beta)

KOD İÇERİĞİ : ( Manage-LocalGP.ps1)

Kod: Tümünü seç

# =====================================================================
# Local Group Policy Management Script
# Backup / Restore / Reset to default / Cross-system Import
# Windows 11 / 10 / 8.1 / 7 - PowerShell 5.1 and 7.x compatible
#
# IMPORTANT NOTES:
# - "Reset" reproduces the classic offline-repair trick of deleting
#   C:\Windows\System32\GroupPolicy and GroupPolicyUsers so Windows
#   regenerates them empty (all local policies back to Not Configured).
# - "Import" reads Registry.pol files (the binary format behind Local
#   Group Policy, per the MS-GPREG specification) from another Windows
#   installation, checks each setting against this machine's own ADMX
#   policy definitions, and only merges settings recognized locally.
#   This compatibility check is a best-effort heuristic based on
#   whether the registry key/value pair is defined in a local .admx
#   file. It cannot verify that the underlying OS feature actually
#   exists, only that the policy engine on this system understands the
#   setting. Always verify results afterward with:
#     gpresult /h report.html
#     gpupdate /force
# - For a Microsoft-supported alternative for local GPO backup/export/
#   import, see the official LGPO.exe tool (Security Compliance
#   Toolkit). This script is a custom, unofficial implementation.
# =====================================================================

[CmdletBinding(DefaultParameterSetName = 'Info')]
param(
    [Parameter(ParameterSetName = 'Backup', Mandatory = $true)]
    [switch]$Backup,

    [Parameter(ParameterSetName = 'Restore', Mandatory = $true)]
    [switch]$Restore,

    [Parameter(ParameterSetName = 'Reset', Mandatory = $true)]
    [switch]$Reset,

    [Parameter(ParameterSetName = 'Import', Mandatory = $true)]
    [switch]$Import,

    [Parameter(ParameterSetName = 'Restore', Mandatory = $true)]
    [string]$RestoreFrom,

    [Parameter(ParameterSetName = 'Import', Mandatory = $true)]
    [string]$ImportFrom,

    [string]$BackupFolder = "$env:ProgramData\LocalGPBackup",
    [string]$LogFolder,
    [switch]$DryRun,
    [switch]$Force
)

# ---------------------------------------------------------------------
# Fixed paths
# ---------------------------------------------------------------------
$GPRoot = Join-Path -Path $env:WinDir -ChildPath 'System32\GroupPolicy'
$GPUsersRoot = Join-Path -Path $env:WinDir -ChildPath 'System32\GroupPolicyUsers'

# ---------------------------------------------------------------------
# Logging setup
# ---------------------------------------------------------------------
$script:LogFilePath = $null
if ($LogFolder) {
    try {
        if (-not (Test-Path -Path $LogFolder)) {
            New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null
        }
        $LogFileName = "Manage-LocalGP_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
        $script:LogFilePath = Join-Path -Path $LogFolder -ChildPath $LogFileName
        New-Item -Path $script:LogFilePath -ItemType File -Force | Out-Null
    }
    catch {
        Write-Warning "ERROR: Could not create log file in '$LogFolder': $($_.Exception.Message)"
        Write-Warning "Continuing without file logging."
        $script:LogFilePath = $null
    }
}

function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ConsoleColor]$Color = 'Gray'
    )
    Write-Host $Message -ForegroundColor $Color
    if ($script:LogFilePath) {
        $Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        Add-Content -Path $script:LogFilePath -Value "[$Timestamp] $Message"
    }
}

# ---------------------------------------------------------------------
# Registry.pol binary format helpers (MS-GPREG)
# Layout: 'PReg' signature, DWORD version=1, then repeated records:
#   [ key ; value ; type(DWORD) ; size(DWORD) ; data(size bytes) ]
# '[' ']' ';' are single UTF-16LE characters; key/value are
# null-terminated UTF-16LE strings.
# ---------------------------------------------------------------------
function Read-Utf16String {
    param(
        [byte[]]$Data,
        [ref]$Offset
    )
    $chars = New-Object System.Collections.Generic.List[char]
    while ($Offset.Value + 1 -lt $Data.Length) {
        $code = [BitConverter]::ToUInt16($Data, $Offset.Value)
        $Offset.Value += 2
        if ($code -eq 0) { break }
        $chars.Add([char]$code)
    }
    return (-join $chars)
}

function Write-Utf16String {
    param(
        [System.IO.BinaryWriter]$Writer,
        [string]$Text
    )
    foreach ($ch in $Text.ToCharArray()) {
        $Writer.Write([UInt16][char]$ch)
    }
    $Writer.Write([UInt16]0)
}

function Read-RegistryPolFile {
    param([Parameter(Mandatory)][string]$Path)

    $entries = @()
    if (-not (Test-Path -Path $Path)) { return $entries }

    $bytes = [System.IO.File]::ReadAllBytes($Path)
    if ($bytes.Length -lt 8) { return $entries }

    if ($bytes[0] -ne 0x50 -or $bytes[1] -ne 0x52 -or $bytes[2] -ne 0x65 -or $bytes[3] -ne 0x67) {
        Write-Log "  WARNING: '$Path' does not have a valid PReg signature, skipping." -Color Yellow
        return $entries
    }

    $offset = 8

    while ($offset + 1 -lt $bytes.Length) {
        $bracket = [BitConverter]::ToUInt16($bytes, $offset)
        if ($bracket -ne 0x005B) { break }
        $offset += 2

        $key = Read-Utf16String -Data $bytes -Offset ([ref]$offset)
        $offset += 2

        $valueName = Read-Utf16String -Data $bytes -Offset ([ref]$offset)
        $offset += 2

        if ($offset + 4 -gt $bytes.Length) { break }
        $type = [BitConverter]::ToUInt32($bytes, $offset)
        $offset += 4
        $offset += 2

        if ($offset + 4 -gt $bytes.Length) { break }
        $size = [BitConverter]::ToUInt32($bytes, $offset)
        $offset += 4
        $offset += 2

        $data = @()
        if ($size -gt 0) {
            if ($offset + $size -gt $bytes.Length) { break }
            $data = $bytes[$offset..($offset + $size - 1)]
        }
        $offset += [int]$size
        $offset += 2

        $entries += [PSCustomObject]@{
            KeyPath   = $key
            ValueName = $valueName
            Type      = $type
            Data      = $data
        }
    }

    return $entries
}

function Write-RegistryPolFile {
    param(
        [Parameter(Mandatory)][string]$Path,
        [Parameter(Mandatory)][array]$Entries
    )

    $stream = New-Object System.IO.MemoryStream
    $writer = New-Object System.IO.BinaryWriter($stream)

    $writer.Write([byte[]](0x50, 0x52, 0x65, 0x67))
    $writer.Write([UInt32]1)

    foreach ($e in $Entries) {
        $writer.Write([UInt16]0x005B)
        Write-Utf16String -Writer $writer -Text $e.KeyPath
        $writer.Write([UInt16]0x003B)
        Write-Utf16String -Writer $writer -Text $e.ValueName
        $writer.Write([UInt16]0x003B)
        $writer.Write([UInt32]$e.Type)
        $writer.Write([UInt16]0x003B)
        $dataLength = 0
        if ($e.Data) { $dataLength = $e.Data.Length }
        $writer.Write([UInt32]$dataLength)
        $writer.Write([UInt16]0x003B)
        if ($dataLength -gt 0) { $writer.Write([byte[]]$e.Data) }
        $writer.Write([UInt16]0x005D)
    }

    $writer.Flush()
    $folder = Split-Path -Path $Path -Parent
    if ($folder -and -not (Test-Path -Path $folder)) {
        New-Item -Path $folder -ItemType Directory -Force | Out-Null
    }
    [System.IO.File]::WriteAllBytes($Path, $stream.ToArray())
    $writer.Close()
    $stream.Close()
}

function Update-GptVersion {
    param([string]$GptIniPath)

    if (-not (Test-Path -Path $GptIniPath)) {
        $folder = Split-Path -Path $GptIniPath -Parent
        if (-not (Test-Path -Path $folder)) { New-Item -Path $folder -ItemType Directory -Force | Out-Null }
        Set-Content -Path $GptIniPath -Value "[General]`r`nVersion=65537`r`n" -Encoding ASCII
        return
    }

    $lines = Get-Content -Path $GptIniPath
    $newLines = @()
    $versionUpdated = $false
    foreach ($line in $lines) {
        if ($line -match '^Version=(\d+)$') {
            $currentVersion = [uint32]$Matches[1]
            # Version packs Machine (low word) and User (high word) counters.
            # Adding 0x10001 (65537) bumps both halves by one.
            $newVersion = $currentVersion + 65537
            $newLines += "Version=$newVersion"
            $versionUpdated = $true
        }
        else {
            $newLines += $line
        }
    }
    if (-not $versionUpdated) { $newLines += 'Version=65537' }
    Set-Content -Path $GptIniPath -Value $newLines -Encoding ASCII
}

function Get-LocalAdmxKnownKeys {
    $admxPath = Join-Path -Path $env:WinDir -ChildPath 'PolicyDefinitions'
    $known = New-Object 'System.Collections.Generic.HashSet[string]'
    if (-not (Test-Path -Path $admxPath)) { return $known }

    $admxFiles = Get-ChildItem -Path $admxPath -Filter *.admx -File -ErrorAction SilentlyContinue
    foreach ($file in $admxFiles) {
        try {
            [xml]$xml = Get-Content -Path $file.FullName -Raw -ErrorAction Stop
        }
        catch {
            continue
        }

        $policyNodes = $xml.GetElementsByTagName('policy')
        foreach ($policy in $policyNodes) {
            $policyKey = $policy.key
            if ([string]::IsNullOrEmpty($policyKey)) { continue }

            if (-not [string]::IsNullOrEmpty($policy.valueName)) {
                [void]$known.Add(("$policyKey|$($policy.valueName)").ToLowerInvariant())
            }
            [void]$known.Add(("$policyKey|").ToLowerInvariant())

            $elementsNode = $policy.elements
            if ($elementsNode) {
                foreach ($el in $elementsNode.ChildNodes) {
                    if ($el.valueName) {
                        $elKey = if ($el.key) { $el.key } else { $policyKey }
                        [void]$known.Add(("$elKey|$($el.valueName)").ToLowerInvariant())
                    }
                }
            }
        }
    }

    return $known
}

function Test-PolicyCompatibility {
    param(
        [Parameter(Mandatory)]$Entry,
        [Parameter(Mandatory)]$KnownKeys
    )
    $exactLookup = ("$($Entry.KeyPath)|$($Entry.ValueName)").ToLowerInvariant()
    $keyOnlyLookup = ("$($Entry.KeyPath)|").ToLowerInvariant()
    return ($KnownKeys.Contains($exactLookup) -or $KnownKeys.Contains($keyOnlyLookup))
}

# ---------------------------------------------------------------------
# Core actions
# ---------------------------------------------------------------------
function Backup-LocalGP {
    param(
        [Parameter(Mandatory)][string]$Destination,
        [switch]$DryRunMode
    )

    $stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
    $target = Join-Path -Path $Destination -ChildPath "GPBackup_$stamp"

    Write-Log "Backup target: $target" -Color Cyan

    if ($DryRunMode) {
        Write-Log "  DRY RUN: would copy '$GPRoot' -> '$target\GroupPolicy'" -Color Magenta
        Write-Log "  DRY RUN: would copy '$GPUsersRoot' -> '$target\GroupPolicyUsers'" -Color Magenta
        return $target
    }

    New-Item -Path $target -ItemType Directory -Force | Out-Null

    if (Test-Path -Path $GPRoot) {
        Copy-Item -Path $GPRoot -Destination (Join-Path $target 'GroupPolicy') -Recurse -Force
        Write-Log "  Copied: $GPRoot" -Color Green
    }
    else {
        Write-Log "  Not found, skipped: $GPRoot" -Color DarkYellow
    }

    if (Test-Path -Path $GPUsersRoot) {
        Copy-Item -Path $GPUsersRoot -Destination (Join-Path $target 'GroupPolicyUsers') -Recurse -Force
        Write-Log "  Copied: $GPUsersRoot" -Color Green
    }
    else {
        Write-Log "  Not found, skipped: $GPUsersRoot" -Color DarkYellow
    }

    Write-Log "Backup completed: $target" -Color Green
    return $target
}

function Restore-LocalGP {
    param(
        [Parameter(Mandatory)][string]$Source,
        [switch]$DryRunMode
    )

    $srcGP = Join-Path -Path $Source -ChildPath 'GroupPolicy'
    $srcGPUsers = Join-Path -Path $Source -ChildPath 'GroupPolicyUsers'

    if (-not (Test-Path -Path $srcGP) -and -not (Test-Path -Path $srcGPUsers)) {
        Write-Log "ERROR: '$Source' does not look like a valid backup (expected 'GroupPolicy' and/or 'GroupPolicyUsers' subfolders)." -Color Red
        return $false
    }

    if ($DryRunMode) {
        Write-Log "  DRY RUN: would remove '$GPRoot' and '$GPUsersRoot', then restore from '$Source'" -Color Magenta
        return $true
    }

    if (Test-Path -Path $srcGP) {
        if (Test-Path -Path $GPRoot) { Remove-Item -Path $GPRoot -Recurse -Force }
        Copy-Item -Path $srcGP -Destination $GPRoot -Recurse -Force
        Write-Log "  Restored: $GPRoot" -Color Green
    }

    if (Test-Path -Path $srcGPUsers) {
        if (Test-Path -Path $GPUsersRoot) { Remove-Item -Path $GPUsersRoot -Recurse -Force }
        Copy-Item -Path $srcGPUsers -Destination $GPUsersRoot -Recurse -Force
        Write-Log "  Restored: $GPUsersRoot" -Color Green
    }

    Write-Log "Restore completed from: $Source" -Color Green
    return $true
}

function Reset-LocalGP {
    param([switch]$DryRunMode)

    if ($DryRunMode) {
        Write-Log "  DRY RUN: would delete '$GPRoot'" -Color Magenta
        Write-Log "  DRY RUN: would delete '$GPUsersRoot'" -Color Magenta
        return
    }

    if (Test-Path -Path $GPRoot) {
        Remove-Item -Path $GPRoot -Recurse -Force
        Write-Log "  Deleted: $GPRoot" -Color Green
    }
    else {
        Write-Log "  Already absent: $GPRoot" -Color DarkYellow
    }

    if (Test-Path -Path $GPUsersRoot) {
        Remove-Item -Path $GPUsersRoot -Recurse -Force
        Write-Log "  Deleted: $GPUsersRoot" -Color Green
    }
    else {
        Write-Log "  Already absent: $GPUsersRoot" -Color DarkYellow
    }

    Write-Log "Local Group Policy has been reset to default (empty) state." -Color Green
    Write-Log "Run 'gpupdate /force' or reboot for the change to fully take effect." -Color Cyan
}

function Invoke-GPImport {
    param(
        [Parameter(Mandatory)][array]$Targets,
        [Parameter(Mandatory)]$KnownKeys,
        [switch]$DryRunMode
    )

    $totalCompatible = 0
    $totalIncompatible = 0
    $rejected = @()
    $anyWritten = $false

    foreach ($t in $Targets) {
        if (-not (Test-Path -Path $t.SourceFile)) { continue }

        $sourceEntries = Read-RegistryPolFile -Path $t.SourceFile
        if ($sourceEntries.Count -eq 0) { continue }

        $localEntries = Read-RegistryPolFile -Path $t.LocalFile

        Write-Log "`n[$($t.Label)] $($sourceEntries.Count) setting(s) found in source." -Color Cyan

        $mergedTable = @{}
        foreach ($le in $localEntries) {
            $mergedTable["$($le.KeyPath)|$($le.ValueName)"] = $le
        }

        $changed = $false
        foreach ($se in $sourceEntries) {
            $compatible = Test-PolicyCompatibility -Entry $se -KnownKeys $KnownKeys
            if ($compatible) {
                Write-Log "  [OK]   $($se.KeyPath)\$($se.ValueName)" -Color Green
                $mergedTable["$($se.KeyPath)|$($se.ValueName)"] = $se
                $totalCompatible++
                $changed = $true
            }
            else {
                Write-Log "  [SKIP] $($se.KeyPath)\$($se.ValueName) (not recognized on this system)" -Color Yellow
                $rejected += "$($t.Label): $($se.KeyPath)\$($se.ValueName)"
                $totalIncompatible++
            }
        }

        if (-not $DryRunMode -and $changed) {
            Write-RegistryPolFile -Path $t.LocalFile -Entries ($mergedTable.Values)
            Write-Log "  Written: $($t.LocalFile)" -Color Green
            $anyWritten = $true
        }
    }

    return [PSCustomObject]@{
        Compatible   = $totalCompatible
        Incompatible = $totalIncompatible
        Rejected     = $rejected
        Written      = $anyWritten
    }
}

function Show-Usage {
    Write-Host "Local Group Policy Management Script" -ForegroundColor White
    Write-Host "======================================================" -ForegroundColor DarkGray
    Write-Host "Usage examples:" -ForegroundColor Cyan
    Write-Host "  .\Manage-LocalGP.ps1 -Backup [-BackupFolder <path>] [-LogFolder <path>]"
    Write-Host "  .\Manage-LocalGP.ps1 -Restore -RestoreFrom <backup path> [-Force]"
    Write-Host "  .\Manage-LocalGP.ps1 -Reset [-Force] [-DryRun]"
    Write-Host "  .\Manage-LocalGP.ps1 -Import -ImportFrom <path to source GroupPolicy folder> [-DryRun] [-Force]"
    Write-Host ""
    Write-Host "  -DryRun     Preview actions without changing anything on disk."
    Write-Host "  -Force      Skip confirmation prompts (use with caution)."
    Write-Host "  -LogFolder  Save a timestamped log file of the operation."
    Write-Host ""
    Write-Host "Example -ImportFrom value: E:\Windows\System32\GroupPolicy" -ForegroundColor DarkGray
    Write-Host "(E: being another Windows installation mounted as a drive letter)" -ForegroundColor DarkGray
}

# ---------------------------------------------------------------------
# Administrator check (skipped for -Info and for -DryRun previews)
# ---------------------------------------------------------------------
if ($PSCmdlet.ParameterSetName -ne 'Info' -and -not $DryRun) {
    $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        Write-Warning "ERROR: This script must be run as ADMINISTRATOR for this operation."
        Write-Host "Tip: use -DryRun to preview actions without elevation." -ForegroundColor Cyan
        Read-Host "Press ENTER to exit."
        exit
    }
}

# ---------------------------------------------------------------------
# Main dispatch
# ---------------------------------------------------------------------
switch ($PSCmdlet.ParameterSetName) {
    'Info' {
        Show-Usage
    }

    'Backup' {
        Backup-LocalGP -Destination $BackupFolder -DryRunMode:$DryRun | Out-Null
    }

    'Restore' {
        if (-not $Force -and -not $DryRun) {
            $answer = Read-Host "This will REPLACE the current Local Group Policy with the backup at '$RestoreFrom'. Continue? (Y/N)"
            if ($answer -notmatch '^[Yy]') {
                Write-Log "Restore cancelled by user." -Color Yellow
                Read-Host "`nPress ENTER to exit."
                exit
            }
        }
        if (-not $DryRun) {
            Backup-LocalGP -Destination $BackupFolder | Out-Null
            Write-Log "Safety backup created before restore." -Color DarkGray
        }
        Restore-LocalGP -Source $RestoreFrom -DryRunMode:$DryRun | Out-Null
    }

    'Reset' {
        if (-not $Force -and -not $DryRun) {
            $answer = Read-Host "This will DELETE all Local Group Policy settings on this system. Continue? (Y/N)"
            if ($answer -notmatch '^[Yy]') {
                Write-Log "Reset cancelled by user." -Color Yellow
                Read-Host "`nPress ENTER to exit."
                exit
            }
        }
        if (-not $DryRun) {
            Backup-LocalGP -Destination $BackupFolder | Out-Null
            Write-Log "Safety backup created before reset." -Color DarkGray
        }
        Reset-LocalGP -DryRunMode:$DryRun
    }

    'Import' {
        if (-not (Test-Path -Path $ImportFrom)) {
            Write-Log "ERROR: Source path not found: $ImportFrom" -Color Red
            Read-Host "`nPress ENTER to exit."
            exit
        }

        $targets = @()
        $targets += @{ Label = 'Machine'; SourceFile = (Join-Path $ImportFrom 'Machine\Registry.pol'); LocalFile = (Join-Path $GPRoot 'Machine\Registry.pol') }
        $targets += @{ Label = 'User'; SourceFile = (Join-Path $ImportFrom 'User\Registry.pol'); LocalFile = (Join-Path $GPRoot 'User\Registry.pol') }

        $importParent = Split-Path -Path $ImportFrom -Parent
        $sourceGPUsers = Join-Path -Path $importParent -ChildPath 'GroupPolicyUsers'
        if (Test-Path -Path $sourceGPUsers) {
            Get-ChildItem -Path $sourceGPUsers -Directory | ForEach-Object {
                $sid = $_.Name
                foreach ($scope in @('Machine', 'User')) {
                    $srcFile = Join-Path -Path $_.FullName -ChildPath "$scope\Registry.pol"
                    if (Test-Path -Path $srcFile) {
                        $targets += @{
                            Label      = "GroupPolicyUsers\$sid\$scope"
                            SourceFile = $srcFile
                            LocalFile  = Join-Path -Path $GPUsersRoot -ChildPath "$sid\$scope\Registry.pol"
                        }
                    }
                }
            }
        }

        if (-not $Force -and -not $DryRun) {
            $answer = Read-Host "This will MERGE compatible policy settings from '$ImportFrom' into this system. Continue? (Y/N)"
            if ($answer -notmatch '^[Yy]') {
                Write-Log "Import cancelled by user." -Color Yellow
                Read-Host "`nPress ENTER to exit."
                exit
            }
        }

        if (-not $DryRun) {
            Backup-LocalGP -Destination $BackupFolder | Out-Null
            Write-Log "Safety backup created before import." -Color DarkGray
        }

        Write-Log "Scanning local ADMX definitions for compatibility reference..." -Color Cyan
        $knownKeys = Get-LocalAdmxKnownKeys
        Write-Log "Loaded $($knownKeys.Count) known policy key/value definitions." -Color DarkGray

        $result = Invoke-GPImport -Targets $targets -KnownKeys $knownKeys -DryRunMode:$DryRun

        if ($result.Written) {
            Update-GptVersion -GptIniPath (Join-Path $GPRoot 'gpt.ini')
        }

        Write-Log "`n======================================================" -Color DarkGray
        Write-Log " IMPORT SUMMARY" -Color White
        Write-Log "======================================================" -Color DarkGray
        Write-Log " Compatible (added/updated) : $($result.Compatible)" -Color Green
        Write-Log " Incompatible (rejected)    : $($result.Incompatible)" -Color Yellow

        if ($result.Rejected.Count -gt 0) {
            Write-Log "`nRejected settings (not recognized by this system's ADMX definitions):" -Color Yellow
            foreach ($r in $result.Rejected) {
                Write-Log "  - $r" -Color Yellow
            }
        }

        if (-not $DryRun) {
            Write-Log "`nRun 'gpupdate /force' and check 'gpresult /h report.html' to verify the result." -Color Cyan
        }
    }
}

Read-Host "`nPress ENTER to exit."
EKRAN GÖRÜNTÜSÜ : (Canlı Sistem : Win11.24H2.R7019 X64 OS Home TR)
Resim
Denemelerini yapıp buraya nihai kararlı sürümünü konu anlatım ile yapacağım...
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

Manage-LocalGP.ps1 Stableştirildi Ve Adı "Manage LocalGroupPolicy.ps1"oldu

Mesaj gönderen TRWE_2012 »

KONU ANLATIM -Versiyon 1-
Resim
KOD İÇERİĞİ : ( Manage LocalGroupPolicy v1.0.ps1 )

Kod: Tümünü seç

# =====================================================================
# Local Group Policy Management Script
# Actions: Backup / Restore / Reset / Import
# Compatible with Windows 11 / 10 / 8.1 / 7
# PowerShell 5.1 and 7.x compatible
# =====================================================================
#
# Reset mirrors the well-known offline technique of deleting:
#   C:\Windows\System32\GroupPolicy
#   C:\Windows\System32\GroupPolicyUsers
# which are normally removed from Windows Recovery / installation media
# (X:\Sources> RD /S /Q ...) to force local policy back to its
# unconfigured default state.
#
# Import reads the Registry.pol file(s) from ANOTHER Windows
# installation's GroupPolicy folder (for example a dual-boot partition,
# a mounted VHD, or an offline image), checks each policy entry against
# the ADMX templates installed on THIS system, and merges only the
# entries that correspond to a policy actually defined here. Entries
# with no matching local ADMX definition are rejected and logged.
#
# NOTE ON COMPATIBILITY CHECKING: this is a best-effort heuristic check
# based on matching registry Key/ValueName pairs against the local
# PolicyDefinitions (ADMX) folder. It confirms that a policy EXISTS on
# this system, but does not validate value ranges, OS edition
# restrictions, or feature availability. Always review the accepted/
# rejected list before trusting the result on a production system.
# =====================================================================

[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidateSet("Backup", "Restore", "Reset", "Import")]
    [string]$Action,

    [string]$BackupFolder,

    [string]$SourceGPOPath,

    [switch]$SkipAutoBackup,

    [switch]$DryRun,

    [string]$LogFolder
)

$script:PolSignature = 0x67655250
$script:LogFilePath = $null

# ---------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------
function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ConsoleColor]$Color = 'Gray'
    )
    Write-Host $Message -ForegroundColor $Color
    if ($script:LogFilePath) {
        $Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        Add-Content -Path $script:LogFilePath -Value "[$Timestamp] $Message"
    }
}

function Initialize-Logging {
    param([string]$Folder)
    if (-not $Folder) { return }
    try {
        if (-not (Test-Path -Path $Folder)) {
            New-Item -Path $Folder -ItemType Directory -Force | Out-Null
        }
        $LogFileName = "GPO-Manage_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
        $script:LogFilePath = Join-Path -Path $Folder -ChildPath $LogFileName
        New-Item -Path $script:LogFilePath -ItemType File -Force | Out-Null
    }
    catch {
        Write-Warning "ERROR: Could not create log file in '$Folder': $($_.Exception.Message)"
        $script:LogFilePath = $null
    }
}

# ---------------------------------------------------------------------
# Registry.pol binary reader / writer
# Format: [Signature(Int32)][Version(Int32)]
#         repeated: '[' Key NUL ';' ValueName NUL ';' Type(Int32) ';'
#                   Size(Int32) ';' Data(Size bytes) ']'
# All strings and bracket/semicolon markers are UTF-16LE.
# ---------------------------------------------------------------------
function Read-PolChar {
    param($Reader)
    return [char]$Reader.ReadUInt16()
}

function Read-PolString {
    param($Reader)
    $Chars = New-Object System.Collections.Generic.List[char]
    while ($true) {
        $Value = $Reader.ReadUInt16()
        if ($Value -eq 0) { break }
        $Chars.Add([char]$Value)
    }
    return -join $Chars
}

function Write-PolChar {
    param($Writer, [char]$Char)
    $Writer.Write([System.Text.Encoding]::Unicode.GetBytes([string]$Char))
}

function Write-PolString {
    param($Writer, [string]$Text)
    if ($Text) {
        $Writer.Write([System.Text.Encoding]::Unicode.GetBytes($Text))
    }
    $Writer.Write([UInt16]0)
}

function Read-PolFile {
    param([Parameter(Mandatory)][string]$Path)

    $Entries = @()
    if (-not (Test-Path -Path $Path)) {
        return $Entries
    }

    $Stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $Reader = New-Object System.IO.BinaryReader($Stream)

    try {
        $Signature = $Reader.ReadInt32()
        $null = $Reader.ReadInt32()  # version, not used

        if ($Signature -ne $script:PolSignature) {
            Write-Log "  WARNING: '$Path' does not look like a valid Registry.pol file. Skipping." -Color Yellow
            return $Entries
        }

        while ($Reader.BaseStream.Position -lt $Reader.BaseStream.Length) {
            $OpenBracket = Read-PolChar -Reader $Reader
            if ($OpenBracket -ne '[') { break }

            $Key = Read-PolString -Reader $Reader
            Read-PolChar -Reader $Reader | Out-Null   # ';'
            $ValueName = Read-PolString -Reader $Reader
            Read-PolChar -Reader $Reader | Out-Null   # ';'
            $Type = $Reader.ReadInt32()
            Read-PolChar -Reader $Reader | Out-Null   # ';'
            $Size = $Reader.ReadInt32()
            Read-PolChar -Reader $Reader | Out-Null   # ';'

            $Data = @()
            if ($Size -gt 0) {
                $Data = $Reader.ReadBytes($Size)
            }

            Read-PolChar -Reader $Reader | Out-Null   # ']'

            $Entries += [PSCustomObject]@{
                Key       = $Key
                ValueName = $ValueName
                Type      = $Type
                Data      = $Data
            }
        }
    }
    finally {
        $Reader.Close()
        $Stream.Close()
    }

    return $Entries
}

function Write-PolFile {
    param(
        [Parameter(Mandatory)][string]$Path,
        [Parameter(Mandatory)][array]$Entries
    )

    $Stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
    $Writer = New-Object System.IO.BinaryWriter($Stream)

    try {
        $Writer.Write([Int32]$script:PolSignature)
        $Writer.Write([Int32]1)

        foreach ($Entry in $Entries) {
            Write-PolChar -Writer $Writer -Char '['
            Write-PolString -Writer $Writer -Text $Entry.Key
            Write-PolChar -Writer $Writer -Char ';'
            Write-PolString -Writer $Writer -Text $Entry.ValueName
            Write-PolChar -Writer $Writer -Char ';'
            $Writer.Write([Int32]$Entry.Type)
            Write-PolChar -Writer $Writer -Char ';'
            $DataCount = 0
            if ($Entry.Data) { $DataCount = $Entry.Data.Count }
            $Writer.Write([Int32]$DataCount)
            Write-PolChar -Writer $Writer -Char ';'
            if ($DataCount -gt 0) {
                $Writer.Write([byte[]]$Entry.Data)
            }
            Write-PolChar -Writer $Writer -Char ']'
        }
    }
    finally {
        $Writer.Close()
        $Stream.Close()
    }
}

# ---------------------------------------------------------------------
# ADMX-based compatibility index for the CURRENT system
# ---------------------------------------------------------------------
function Get-LocalPolicyDefinitionIndex {
    $AdmxFolder = Join-Path -Path $env:SystemRoot -ChildPath "PolicyDefinitions"
    $Index = New-Object System.Collections.Generic.HashSet[string]

    if (-not (Test-Path -Path $AdmxFolder)) {
        Write-Log "  WARNING: ADMX folder not found at '$AdmxFolder'. Compatibility check will be limited." -Color Yellow
        return $Index
    }

    $AdmxFiles = Get-ChildItem -Path $AdmxFolder -Filter *.admx -File -ErrorAction SilentlyContinue

    foreach ($AdmxFile in $AdmxFiles) {
        try {
            [xml]$Xml = Get-Content -Path $AdmxFile.FullName -Raw -ErrorAction Stop
            $PolicyNodes = $Xml.policyDefinitions.policies.policy
            foreach ($PolicyNode in $PolicyNodes) {
                $Key = $PolicyNode.key
                if (-not $Key) { continue }

                $ValueNames = @()
                if ($PolicyNode.valueName) { $ValueNames += $PolicyNode.valueName }
                if ($PolicyNode.elements) {
                    if ($PolicyNode.elements.boolean.valueName) { $ValueNames += $PolicyNode.elements.boolean.valueName }
                    if ($PolicyNode.elements.decimal.valueName) { $ValueNames += $PolicyNode.elements.decimal.valueName }
                    if ($PolicyNode.elements.text.valueName) { $ValueNames += $PolicyNode.elements.text.valueName }
                    if ($PolicyNode.elements.enum.valueName) { $ValueNames += $PolicyNode.elements.enum.valueName }
                    if ($PolicyNode.elements.list.valuePrefix) { $ValueNames += $PolicyNode.elements.list.valuePrefix }
                }
                if ($ValueNames.Count -eq 0) { $ValueNames = @("") }

                foreach ($ValueName in $ValueNames) {
                    if ($null -eq $ValueName) { continue }
                    $Index.Add(("{0}|{1}" -f $Key.ToLowerInvariant(), $ValueName.ToLowerInvariant())) | Out-Null
                    # Also index by key alone: list-type elements generate
                    # dynamic value names not literally present in the ADMX.
                    $Index.Add(("{0}|*" -f $Key.ToLowerInvariant())) | Out-Null
                }
            }
        }
        catch {
            Write-Log "  WARNING: Could not parse '$($AdmxFile.Name)': $($_.Exception.Message)" -Color Yellow
        }
    }

    return $Index
}

function Test-PolicyEntryCompatibility {
    param($Entry, $Index)
    $KeyLookup = ("{0}|{1}" -f $Entry.Key.ToLowerInvariant(), $Entry.ValueName.ToLowerInvariant())
    $WildcardLookup = ("{0}|*" -f $Entry.Key.ToLowerInvariant())
    return ($Index.Contains($KeyLookup) -or $Index.Contains($WildcardLookup))
}

# ---------------------------------------------------------------------
# Action implementations
# ---------------------------------------------------------------------
function Invoke-GPOBackup {
    param([string]$TargetBackupFolder)

    if (-not $TargetBackupFolder) { $TargetBackupFolder = Join-Path $env:SystemDrive "GPO-Backup" }
    $Timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
    $Destination = Join-Path $TargetBackupFolder "GroupPolicy_Backup_$Timestamp"

    Write-Log "Backing up local Group Policy folders to '$Destination'..." -Color Cyan

    if ($DryRun) {
        Write-Log "  DRY RUN: would copy '$GPRoot' and '$GPUsersRoot' to '$Destination'." -Color Magenta
        return $Destination
    }

    New-Item -Path $Destination -ItemType Directory -Force | Out-Null

    if (Test-Path -Path $GPRoot) {
        Copy-Item -Path $GPRoot -Destination (Join-Path $Destination "GroupPolicy") -Recurse -Force
        Write-Log "  Copied: GroupPolicy" -Color Green
    }
    else {
        Write-Log "  GroupPolicy folder not found, nothing to back up." -Color Yellow
    }

    if (Test-Path -Path $GPUsersRoot) {
        Copy-Item -Path $GPUsersRoot -Destination (Join-Path $Destination "GroupPolicyUsers") -Recurse -Force
        Write-Log "  Copied: GroupPolicyUsers" -Color Green
    }
    else {
        Write-Log "  GroupPolicyUsers folder not found, nothing to back up." -Color Yellow
    }

    Write-Log "  Backup complete: $Destination" -Color Green
    return $Destination
}

function Invoke-GPORestore {
    param([string]$SourceBackupFolder)

    if (-not $SourceBackupFolder -or -not (Test-Path -Path $SourceBackupFolder)) {
        Write-Warning "ERROR: -BackupFolder must point to a specific existing backup folder (e.g. '...\GroupPolicy_Backup_20260821_143512')."
        Read-Host "Press ENTER to exit."
        exit
    }

    $SourceGP = Join-Path $SourceBackupFolder "GroupPolicy"
    $SourceGPUsers = Join-Path $SourceBackupFolder "GroupPolicyUsers"

    Write-Log "Restoring Group Policy folders from '$SourceBackupFolder'..." -Color Cyan

    if ($DryRun) {
        Write-Log "  DRY RUN: would restore '$SourceGP' -> '$GPRoot'." -Color Magenta
        Write-Log "  DRY RUN: would restore '$SourceGPUsers' -> '$GPUsersRoot'." -Color Magenta
        return
    }

    if (Test-Path -Path $SourceGP) {
        if (Test-Path -Path $GPRoot) { Remove-Item -Path $GPRoot -Recurse -Force }
        Copy-Item -Path $SourceGP -Destination $GPRoot -Recurse -Force
        Write-Log "  Restored: GroupPolicy" -Color Green
    }
    else {
        Write-Log "  No GroupPolicy folder in backup, skipped." -Color Yellow
    }

    if (Test-Path -Path $SourceGPUsers) {
        if (Test-Path -Path $GPUsersRoot) { Remove-Item -Path $GPUsersRoot -Recurse -Force }
        Copy-Item -Path $SourceGPUsers -Destination $GPUsersRoot -Recurse -Force
        Write-Log "  Restored: GroupPolicyUsers" -Color Green
    }
    else {
        Write-Log "  No GroupPolicyUsers folder in backup, skipped." -Color Yellow
    }

    Write-Log "  Running gpupdate /force to apply restored policies..." -Color Cyan
    Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -WindowStyle Hidden
    Write-Log "  Restore complete." -Color Green
}

function Invoke-GPOReset {
    Write-Log "Resetting local Group Policy to its default (unconfigured) state..." -Color Cyan
    Write-Log "NOTE: this mirrors the RD /S /Q technique normally run from Windows" -Color DarkGray
    Write-Log "Recovery / installation media. Running it from a live session works" -Color DarkGray
    Write-Log "in most cases. If a file is reported as locked, boot into WinRE /" -Color DarkGray
    Write-Log "Setup and run these two commands instead:" -Color DarkGray
    Write-Log "  RD /S /Q `"$GPRoot`"" -Color DarkGray
    Write-Log "  RD /S /Q `"$GPUsersRoot`"" -Color DarkGray

    if (-not $SkipAutoBackup) {
        Invoke-GPOBackup -TargetBackupFolder $(if ($BackupFolder) { $BackupFolder } else { Join-Path $env:SystemDrive "GPO-Backup" }) | Out-Null
    }

    if ($DryRun) {
        Write-Log "  DRY RUN: would delete '$GPRoot' and '$GPUsersRoot'." -Color Magenta
        return
    }

    try {
        if (Test-Path -Path $GPRoot) {
            Remove-Item -Path $GPRoot -Recurse -Force -ErrorAction Stop
            Write-Log "  Removed: GroupPolicy" -Color Green
        }
        if (Test-Path -Path $GPUsersRoot) {
            Remove-Item -Path $GPUsersRoot -Recurse -Force -ErrorAction Stop
            Write-Log "  Removed: GroupPolicyUsers" -Color Green
        }
        Write-Log "  Running gpupdate /force to apply the reset..." -Color Cyan
        Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -WindowStyle Hidden
        Write-Log "  Reset complete. Local Group Policy is back to its default state." -Color Green
    }
    catch {
        Write-Log "  ERROR: $($_.Exception.Message)" -Color Red
        Write-Log "  A file may be locked by the policy engine. Boot into Windows" -Color Yellow
        Write-Log "  Recovery / installation media and run the RD /S /Q commands above." -Color Yellow
    }
}

function Invoke-GPOImport {
    if (-not $SourceGPOPath -or -not (Test-Path -Path $SourceGPOPath)) {
        Write-Warning "ERROR: -SourceGPOPath must point to another installation's GroupPolicy folder, e.g. 'D:\Windows\System32\GroupPolicy'."
        Read-Host "Press ENTER to exit."
        exit
    }

    Write-Log "Building local policy definition index from ADMX templates..." -Color Cyan
    $Index = Get-LocalPolicyDefinitionIndex
    Write-Log "Index built: $($Index.Count) known key/value combinations on this system." -Color Cyan

    $Pairs = @(
        @{ Name = "Machine"; Source = (Join-Path $SourceGPOPath "Machine\Registry.pol"); Target = (Join-Path $GPRoot "Machine\Registry.pol") },
        @{ Name = "User";    Source = (Join-Path $SourceGPOPath "User\Registry.pol");    Target = (Join-Path $GPRoot "User\Registry.pol") }
    )

    foreach ($Pair in $Pairs) {
        Write-Log "`n--- Processing $($Pair.Name) policies ---" -Color White

        if (-not (Test-Path -Path $Pair.Source)) {
            Write-Log "  No $($Pair.Name)\Registry.pol found at source. Skipping." -Color Yellow
            continue
        }

        $SourceEntries = Read-PolFile -Path $Pair.Source
        $TargetEntries = Read-PolFile -Path $Pair.Target

        $Accepted = @()
        $RejectedCount = 0

        foreach ($Entry in $SourceEntries) {
            if (Test-PolicyEntryCompatibility -Entry $Entry -Index $Index) {
                $Accepted += $Entry
                Write-Log "  ACCEPTED : $($Entry.Key) \ $($Entry.ValueName)" -Color Green
            }
            else {
                $RejectedCount++
                Write-Log "  REJECTED : $($Entry.Key) \ $($Entry.ValueName) (no matching policy on this system)" -Color Red
            }
        }

        Write-Log "  Summary: $($Accepted.Count) compatible, $RejectedCount incompatible." -Color Cyan

        if ($Accepted.Count -eq 0) {
            Write-Log "  Nothing compatible to merge for $($Pair.Name)." -Color Yellow
            continue
        }

        $MergedEntries = @($TargetEntries)
        foreach ($NewEntry in $Accepted) {
            $ExistingIndex = -1
            for ($i = 0; $i -lt $MergedEntries.Count; $i++) {
                if ($MergedEntries[$i].Key -eq $NewEntry.Key -and $MergedEntries[$i].ValueName -eq $NewEntry.ValueName) {
                    $ExistingIndex = $i
                    break
                }
            }
            if ($ExistingIndex -ge 0) {
                $MergedEntries[$ExistingIndex] = $NewEntry
            }
            else {
                $MergedEntries += $NewEntry
            }
        }

        if ($DryRun) {
            Write-Log "  DRY RUN: would write $($MergedEntries.Count) total entries to '$($Pair.Target)'." -Color Magenta
        }
        else {
            $TargetParent = Split-Path -Path $Pair.Target -Parent
            if (-not (Test-Path -Path $TargetParent)) {
                New-Item -Path $TargetParent -ItemType Directory -Force | Out-Null
            }
            Write-PolFile -Path $Pair.Target -Entries $MergedEntries
            Write-Log "  Wrote $($MergedEntries.Count) total entries to '$($Pair.Target)'." -Color Green
        }
    }

    if (-not $DryRun) {
        Write-Log "`nRunning gpupdate /force to apply merged policies..." -Color Cyan
        Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -WindowStyle Hidden
    }
}

# ---------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------
Initialize-Logging -Folder $LogFolder

if ($DryRun) {
    Write-Log "===== DRY RUN MODE: no changes will actually be made =====" -Color Magenta
}
if ($script:LogFilePath) {
    Write-Log "Log file: $script:LogFilePath" -Color DarkGray
}

if (-not $DryRun) {
    $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        Write-Warning "ERROR: This script must be run as ADMINISTRATOR for the '$Action' action."
        Write-Host "Please reopen PowerShell as Administrator and try again." -ForegroundColor Cyan
        Write-Host "Tip: use -DryRun to preview the operation without elevation." -ForegroundColor Cyan
        Read-Host "Press ENTER to exit."
        exit
    }
}

$GPRoot = Join-Path $env:SystemRoot "System32\GroupPolicy"
$GPUsersRoot = Join-Path $env:SystemRoot "System32\GroupPolicyUsers"

switch ($Action) {
    "Backup"  { Invoke-GPOBackup -TargetBackupFolder $BackupFolder | Out-Null }
    "Restore" { Invoke-GPORestore -SourceBackupFolder $BackupFolder }
    "Reset"   { Invoke-GPOReset }
    "Import"  { Invoke-GPOImport }
}

if ($script:LogFilePath) {
    Write-Log "`nFull log saved to: $script:LogFilePath" -Color DarkGray
}

Read-Host "`nPress ENTER to exit."
KONU ANLATIM -Versiyon 2-
Resim
KOD İÇERİĞİ : ( Manage LocalGroupPolicy v2.0.ps1 )

Kod: Tümünü seç

# =====================================================================
# Local Group Policy Management Script
# Actions: Backup / Restore / Reset / Import
# Compatible with Windows 11 / 10 / 8.1 / 7
# PowerShell 5.1 and 7.x compatible
# =====================================================================
#
# Reset mirrors the well-known offline technique of deleting:
#   C:\Windows\System32\GroupPolicy
#   C:\Windows\System32\GroupPolicyUsers
# which are normally removed from Windows Recovery / installation media
# (X:\Sources> RD /S /Q ...) to force local policy back to its
# unconfigured default state.
#
# Import reads the Registry.pol file(s) from ANOTHER Windows source,
# checks each policy entry against the ADMX templates installed on
# THIS system, and merges only the entries that correspond to a policy
# actually defined here. Entries with no matching local ADMX
# definition are rejected and logged. The source can be either:
#   -SourceGPOPath  : an already-accessible GroupPolicy folder, e.g. a
#                      dual-boot partition ('D:\Windows\System32\GroupPolicy')
#   -SourceISO      : a Windows installation .iso file. The script
#                      mounts the ISO, locates sources\install.wim (or
#                      .esd), mounts the selected edition read-only via
#                      DISM, and reads GroupPolicy from inside it, then
#                      cleans up both mounts automatically.
#
# NOTE: a stock/unmodified Windows installation image normally has NO
# local Group Policy configured (no GroupPolicy folder at all), since
# local GPOs only exist once gpedit.msc has been used, or a custom/
# reference image was built with such settings baked in. Importing
# from a plain retail ISO will typically report "nothing found".
#
# NOTE ON ISO/WIM SUPPORT: this relies on the built-in Storage and
# Dism PowerShell modules (Mount-DiskImage, Get-WindowsImage,
# Mount-WindowsImage), which ship with Windows 8 / Server 2012 and
# later as the HOST running this script. A Windows 7 host cannot use
# -SourceISO through these cmdlets; extract the ISO with a third-party
# tool first and use -SourceGPOPath instead. The edition INSIDE the
# ISO (7, 8.1, 10, 11...) is not restricted by this.
#
# NOTE ON COMPATIBILITY CHECKING: this is a best-effort heuristic check
# based on matching registry Key/ValueName pairs against the local
# PolicyDefinitions (ADMX) folder. It confirms that a policy EXISTS on
# this system, but does not validate value ranges, OS edition
# restrictions, or feature availability. Always review the accepted/
# rejected list before trusting the result on a production system.
# =====================================================================

[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidateSet("Backup", "Restore", "Reset", "Import")]
    [string]$Action,

    [string]$BackupFolder,

    [string]$SourceGPOPath,

    [string]$SourceISO,

    [int]$SourceWimIndex = 0,

    [switch]$SkipAutoBackup,

    [switch]$DryRun,

    [string]$LogFolder
)

$script:PolSignature = 0x67655250
$script:LogFilePath = $null

# ---------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------
function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ConsoleColor]$Color = 'Gray'
    )
    Write-Host $Message -ForegroundColor $Color
    if ($script:LogFilePath) {
        $Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        Add-Content -Path $script:LogFilePath -Value "[$Timestamp] $Message"
    }
}

function Initialize-Logging {
    param([string]$Folder)
    if (-not $Folder) { return }
    try {
        if (-not (Test-Path -Path $Folder)) {
            New-Item -Path $Folder -ItemType Directory -Force | Out-Null
        }
        $LogFileName = "GPO-Manage_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
        $script:LogFilePath = Join-Path -Path $Folder -ChildPath $LogFileName
        New-Item -Path $script:LogFilePath -ItemType File -Force | Out-Null
    }
    catch {
        Write-Warning "ERROR: Could not create log file in '$Folder': $($_.Exception.Message)"
        $script:LogFilePath = $null
    }
}

# ---------------------------------------------------------------------
# Registry.pol binary reader / writer
# Format: [Signature(Int32)][Version(Int32)]
#         repeated: '[' Key NUL ';' ValueName NUL ';' Type(Int32) ';'
#                   Size(Int32) ';' Data(Size bytes) ']'
# All strings and bracket/semicolon markers are UTF-16LE.
# ---------------------------------------------------------------------
function Read-PolChar {
    param($Reader)
    return [char]$Reader.ReadUInt16()
}

function Read-PolString {
    param($Reader)
    $Chars = New-Object System.Collections.Generic.List[char]
    while ($true) {
        $Value = $Reader.ReadUInt16()
        if ($Value -eq 0) { break }
        $Chars.Add([char]$Value)
    }
    return -join $Chars
}

function Write-PolChar {
    param($Writer, [char]$Char)
    $Writer.Write([System.Text.Encoding]::Unicode.GetBytes([string]$Char))
}

function Write-PolString {
    param($Writer, [string]$Text)
    if ($Text) {
        $Writer.Write([System.Text.Encoding]::Unicode.GetBytes($Text))
    }
    $Writer.Write([UInt16]0)
}

function Read-PolFile {
    param([Parameter(Mandatory)][string]$Path)

    $Entries = @()
    if (-not (Test-Path -Path $Path)) {
        return $Entries
    }

    $Stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $Reader = New-Object System.IO.BinaryReader($Stream)

    try {
        $Signature = $Reader.ReadInt32()
        $null = $Reader.ReadInt32()  # version, not used

        if ($Signature -ne $script:PolSignature) {
            Write-Log "  WARNING: '$Path' does not look like a valid Registry.pol file. Skipping." -Color Yellow
            return $Entries
        }

        while ($Reader.BaseStream.Position -lt $Reader.BaseStream.Length) {
            $OpenBracket = Read-PolChar -Reader $Reader
            if ($OpenBracket -ne '[') { break }

            $Key = Read-PolString -Reader $Reader
            Read-PolChar -Reader $Reader | Out-Null   # ';'
            $ValueName = Read-PolString -Reader $Reader
            Read-PolChar -Reader $Reader | Out-Null   # ';'
            $Type = $Reader.ReadInt32()
            Read-PolChar -Reader $Reader | Out-Null   # ';'
            $Size = $Reader.ReadInt32()
            Read-PolChar -Reader $Reader | Out-Null   # ';'

            $Data = @()
            if ($Size -gt 0) {
                $Data = $Reader.ReadBytes($Size)
            }

            Read-PolChar -Reader $Reader | Out-Null   # ']'

            $Entries += [PSCustomObject]@{
                Key       = $Key
                ValueName = $ValueName
                Type      = $Type
                Data      = $Data
            }
        }
    }
    finally {
        $Reader.Close()
        $Stream.Close()
    }

    return $Entries
}

function Write-PolFile {
    param(
        [Parameter(Mandatory)][string]$Path,
        [Parameter(Mandatory)][array]$Entries
    )

    $Stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
    $Writer = New-Object System.IO.BinaryWriter($Stream)

    try {
        $Writer.Write([Int32]$script:PolSignature)
        $Writer.Write([Int32]1)

        foreach ($Entry in $Entries) {
            Write-PolChar -Writer $Writer -Char '['
            Write-PolString -Writer $Writer -Text $Entry.Key
            Write-PolChar -Writer $Writer -Char ';'
            Write-PolString -Writer $Writer -Text $Entry.ValueName
            Write-PolChar -Writer $Writer -Char ';'
            $Writer.Write([Int32]$Entry.Type)
            Write-PolChar -Writer $Writer -Char ';'
            $DataCount = 0
            if ($Entry.Data) { $DataCount = $Entry.Data.Count }
            $Writer.Write([Int32]$DataCount)
            Write-PolChar -Writer $Writer -Char ';'
            if ($DataCount -gt 0) {
                $Writer.Write([byte[]]$Entry.Data)
            }
            Write-PolChar -Writer $Writer -Char ']'
        }
    }
    finally {
        $Writer.Close()
        $Stream.Close()
    }
}

# ---------------------------------------------------------------------
# ADMX-based compatibility index for the CURRENT system
# ---------------------------------------------------------------------
function Get-LocalPolicyDefinitionIndex {
    $AdmxFolder = Join-Path -Path $env:SystemRoot -ChildPath "PolicyDefinitions"
    $Index = New-Object System.Collections.Generic.HashSet[string]

    if (-not (Test-Path -Path $AdmxFolder)) {
        Write-Log "  WARNING: ADMX folder not found at '$AdmxFolder'. Compatibility check will be limited." -Color Yellow
        return $Index
    }

    $AdmxFiles = Get-ChildItem -Path $AdmxFolder -Filter *.admx -File -ErrorAction SilentlyContinue

    foreach ($AdmxFile in $AdmxFiles) {
        try {
            [xml]$Xml = Get-Content -Path $AdmxFile.FullName -Raw -ErrorAction Stop
            $PolicyNodes = $Xml.policyDefinitions.policies.policy
            foreach ($PolicyNode in $PolicyNodes) {
                $Key = $PolicyNode.key
                if (-not $Key) { continue }

                $ValueNames = @()
                if ($PolicyNode.valueName) { $ValueNames += $PolicyNode.valueName }
                if ($PolicyNode.elements) {
                    if ($PolicyNode.elements.boolean.valueName) { $ValueNames += $PolicyNode.elements.boolean.valueName }
                    if ($PolicyNode.elements.decimal.valueName) { $ValueNames += $PolicyNode.elements.decimal.valueName }
                    if ($PolicyNode.elements.text.valueName) { $ValueNames += $PolicyNode.elements.text.valueName }
                    if ($PolicyNode.elements.enum.valueName) { $ValueNames += $PolicyNode.elements.enum.valueName }
                    if ($PolicyNode.elements.list.valuePrefix) { $ValueNames += $PolicyNode.elements.list.valuePrefix }
                }
                if ($ValueNames.Count -eq 0) { $ValueNames = @("") }

                foreach ($ValueName in $ValueNames) {
                    if ($null -eq $ValueName) { continue }
                    $Index.Add(("{0}|{1}" -f $Key.ToLowerInvariant(), $ValueName.ToLowerInvariant())) | Out-Null
                    # Also index by key alone: list-type elements generate
                    # dynamic value names not literally present in the ADMX.
                    $Index.Add(("{0}|*" -f $Key.ToLowerInvariant())) | Out-Null
                }
            }
        }
        catch {
            Write-Log "  WARNING: Could not parse '$($AdmxFile.Name)': $($_.Exception.Message)" -Color Yellow
        }
    }

    return $Index
}

function Test-PolicyEntryCompatibility {
    param($Entry, $Index)
    $KeyLookup = ("{0}|{1}" -f $Entry.Key.ToLowerInvariant(), $Entry.ValueName.ToLowerInvariant())
    $WildcardLookup = ("{0}|*" -f $Entry.Key.ToLowerInvariant())
    return ($Index.Contains($KeyLookup) -or $Index.Contains($WildcardLookup))
}

# ---------------------------------------------------------------------
# ISO / WIM source resolution for Import
# ---------------------------------------------------------------------
function Resolve-GPOSourceFromISO {
    param(
        [Parameter(Mandatory)][string]$IsoPath,
        [int]$WimIndex
    )

    if (-not (Test-Path -Path $IsoPath)) {
        Write-Warning "ERROR: ISO file not found at '$IsoPath'."
        Read-Host "Press ENTER to exit."
        exit
    }

    Write-Log "Mounting ISO: $IsoPath" -Color Cyan
    $DiskImage = Mount-DiskImage -ImagePath $IsoPath -PassThru -ErrorAction Stop
    $Volume = $DiskImage | Get-Volume
    $IsoDriveLetter = ($Volume | Where-Object { $_.DriveLetter } | Select-Object -First 1).DriveLetter

    if (-not $IsoDriveLetter) {
        Write-Warning "ERROR: Could not determine the mounted ISO drive letter."
        Dismount-DiskImage -ImagePath $IsoPath -ErrorAction SilentlyContinue | Out-Null
        Read-Host "Press ENTER to exit."
        exit
    }

    $IsoRoot = "$($IsoDriveLetter):\"
    Write-Log "  ISO mounted at $IsoRoot" -Color Green

    $WimPath = Join-Path $IsoRoot "sources\install.wim"
    $EsdPath = Join-Path $IsoRoot "sources\install.esd"
    $ImagePath = $null
    if (Test-Path -Path $WimPath) { $ImagePath = $WimPath }
    elseif (Test-Path -Path $EsdPath) { $ImagePath = $EsdPath }

    if (-not $ImagePath) {
        Write-Warning "ERROR: No sources\install.wim or sources\install.esd found on '$IsoRoot'."
        Dismount-DiskImage -ImagePath $IsoPath -ErrorAction SilentlyContinue | Out-Null
        Read-Host "Press ENTER to exit."
        exit
    }
    Write-Log "  Found image: $ImagePath" -Color Green

    $Images = Get-WindowsImage -ImagePath $ImagePath

    if ($WimIndex -le 0) {
        if ($Images.Count -eq 1) {
            $WimIndex = $Images[0].ImageIndex
            Write-Log "  Only one edition found, using index $WimIndex ($($Images[0].ImageName))." -Color Cyan
        }
        else {
            Write-Log "  Multiple editions found in this image. Re-run with -SourceWimIndex:" -Color Yellow
            foreach ($Img in $Images) {
                Write-Log ("    Index {0}: {1}" -f $Img.ImageIndex, $Img.ImageName) -Color Yellow
            }
            Dismount-DiskImage -ImagePath $IsoPath -ErrorAction SilentlyContinue | Out-Null
            Read-Host "Press ENTER to exit."
            exit
        }
    }

    $MountPath = Join-Path $env:TEMP "GPO-Import-Mount_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
    New-Item -Path $MountPath -ItemType Directory -Force | Out-Null

    Write-Log "  Mounting Windows image index $WimIndex (read-only)..." -Color Cyan
    Mount-WindowsImage -ImagePath $ImagePath -Index $WimIndex -Path $MountPath -ReadOnly -ErrorAction Stop | Out-Null
    Write-Log "  Image mounted at $MountPath" -Color Green

    return [PSCustomObject]@{
        GPOPath   = Join-Path $MountPath "Windows\System32\GroupPolicy"
        MountPath = $MountPath
        IsoPath   = $IsoPath
    }
}

function Dismount-GPOSourceISO {
    param($MountInfo)
    if (-not $MountInfo) { return }

    Write-Log "Cleaning up: dismounting Windows image and ISO..." -Color Cyan

    try {
        Dismount-WindowsImage -Path $MountInfo.MountPath -Discard -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Log "  WARNING: Could not cleanly dismount Windows image: $($_.Exception.Message)" -Color Yellow
    }

    Remove-Item -Path $MountInfo.MountPath -Recurse -Force -ErrorAction SilentlyContinue

    try {
        Dismount-DiskImage -ImagePath $MountInfo.IsoPath -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Log "  WARNING: Could not cleanly dismount ISO: $($_.Exception.Message)" -Color Yellow
    }

    Write-Log "  Cleanup complete." -Color Green
}

# ---------------------------------------------------------------------
# Action implementations
# ---------------------------------------------------------------------
function Invoke-GPOBackup {
    param([string]$TargetBackupFolder)

    if (-not $TargetBackupFolder) { $TargetBackupFolder = Join-Path $env:SystemDrive "GPO-Backup" }
    $Timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
    $Destination = Join-Path $TargetBackupFolder "GroupPolicy_Backup_$Timestamp"

    Write-Log "Backing up local Group Policy folders to '$Destination'..." -Color Cyan

    if ($DryRun) {
        Write-Log "  DRY RUN: would copy '$GPRoot' and '$GPUsersRoot' to '$Destination'." -Color Magenta
        return $Destination
    }

    New-Item -Path $Destination -ItemType Directory -Force | Out-Null

    if (Test-Path -Path $GPRoot) {
        Copy-Item -Path $GPRoot -Destination (Join-Path $Destination "GroupPolicy") -Recurse -Force
        Write-Log "  Copied: GroupPolicy" -Color Green
    }
    else {
        Write-Log "  GroupPolicy folder not found, nothing to back up." -Color Yellow
    }

    if (Test-Path -Path $GPUsersRoot) {
        Copy-Item -Path $GPUsersRoot -Destination (Join-Path $Destination "GroupPolicyUsers") -Recurse -Force
        Write-Log "  Copied: GroupPolicyUsers" -Color Green
    }
    else {
        Write-Log "  GroupPolicyUsers folder not found, nothing to back up." -Color Yellow
    }

    Write-Log "  Backup complete: $Destination" -Color Green
    return $Destination
}

function Invoke-GPORestore {
    param([string]$SourceBackupFolder)

    if (-not $SourceBackupFolder -or -not (Test-Path -Path $SourceBackupFolder)) {
        Write-Warning "ERROR: -BackupFolder must point to a specific existing backup folder (e.g. '...\GroupPolicy_Backup_20260821_143512')."
        Read-Host "Press ENTER to exit."
        exit
    }

    $SourceGP = Join-Path $SourceBackupFolder "GroupPolicy"
    $SourceGPUsers = Join-Path $SourceBackupFolder "GroupPolicyUsers"

    Write-Log "Restoring Group Policy folders from '$SourceBackupFolder'..." -Color Cyan

    if ($DryRun) {
        Write-Log "  DRY RUN: would restore '$SourceGP' -> '$GPRoot'." -Color Magenta
        Write-Log "  DRY RUN: would restore '$SourceGPUsers' -> '$GPUsersRoot'." -Color Magenta
        return
    }

    if (Test-Path -Path $SourceGP) {
        if (Test-Path -Path $GPRoot) { Remove-Item -Path $GPRoot -Recurse -Force }
        Copy-Item -Path $SourceGP -Destination $GPRoot -Recurse -Force
        Write-Log "  Restored: GroupPolicy" -Color Green
    }
    else {
        Write-Log "  No GroupPolicy folder in backup, skipped." -Color Yellow
    }

    if (Test-Path -Path $SourceGPUsers) {
        if (Test-Path -Path $GPUsersRoot) { Remove-Item -Path $GPUsersRoot -Recurse -Force }
        Copy-Item -Path $SourceGPUsers -Destination $GPUsersRoot -Recurse -Force
        Write-Log "  Restored: GroupPolicyUsers" -Color Green
    }
    else {
        Write-Log "  No GroupPolicyUsers folder in backup, skipped." -Color Yellow
    }

    Write-Log "  Running gpupdate /force to apply restored policies..." -Color Cyan
    Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -WindowStyle Hidden
    Write-Log "  Restore complete." -Color Green
}

function Invoke-GPOReset {
    Write-Log "Resetting local Group Policy to its default (unconfigured) state..." -Color Cyan
    Write-Log "NOTE: this mirrors the RD /S /Q technique normally run from Windows" -Color DarkGray
    Write-Log "Recovery / installation media. Running it from a live session works" -Color DarkGray
    Write-Log "in most cases. If a file is reported as locked, boot into WinRE /" -Color DarkGray
    Write-Log "Setup and run these two commands instead:" -Color DarkGray
    Write-Log "  RD /S /Q `"$GPRoot`"" -Color DarkGray
    Write-Log "  RD /S /Q `"$GPUsersRoot`"" -Color DarkGray

    if (-not $SkipAutoBackup) {
        Invoke-GPOBackup -TargetBackupFolder $(if ($BackupFolder) { $BackupFolder } else { Join-Path $env:SystemDrive "GPO-Backup" }) | Out-Null
    }

    if ($DryRun) {
        Write-Log "  DRY RUN: would delete '$GPRoot' and '$GPUsersRoot'." -Color Magenta
        return
    }

    try {
        if (Test-Path -Path $GPRoot) {
            Remove-Item -Path $GPRoot -Recurse -Force -ErrorAction Stop
            Write-Log "  Removed: GroupPolicy" -Color Green
        }
        if (Test-Path -Path $GPUsersRoot) {
            Remove-Item -Path $GPUsersRoot -Recurse -Force -ErrorAction Stop
            Write-Log "  Removed: GroupPolicyUsers" -Color Green
        }
        Write-Log "  Running gpupdate /force to apply the reset..." -Color Cyan
        Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -WindowStyle Hidden
        Write-Log "  Reset complete. Local Group Policy is back to its default state." -Color Green
    }
    catch {
        Write-Log "  ERROR: $($_.Exception.Message)" -Color Red
        Write-Log "  A file may be locked by the policy engine. Boot into Windows" -Color Yellow
        Write-Log "  Recovery / installation media and run the RD /S /Q commands above." -Color Yellow
    }
}

function Invoke-GPOImport {
    $CleanupMountInfo = $null
    $EffectiveSourcePath = $SourceGPOPath

    if ($SourceISO -and $SourceGPOPath) {
        Write-Log "Both -SourceISO and -SourceGPOPath were given; -SourceISO takes precedence." -Color Yellow
    }

    if ($SourceISO) {
        try {
            $MountInfo = Resolve-GPOSourceFromISO -IsoPath $SourceISO -WimIndex $SourceWimIndex
            $EffectiveSourcePath = $MountInfo.GPOPath
            $CleanupMountInfo = $MountInfo
        }
        catch {
            Write-Warning "ERROR: Could not mount ISO/image: $($_.Exception.Message)"
            Write-Warning "This feature needs the built-in Storage and Dism PowerShell modules"
            Write-Warning "(Windows 8 / Server 2012 or newer HOST). On a Windows 7 host, extract"
            Write-Warning "the ISO with a third-party tool and use -SourceGPOPath instead."
            Read-Host "Press ENTER to exit."
            exit
        }
    }

    if (-not $EffectiveSourcePath) {
        Write-Warning "ERROR: provide either -SourceGPOPath (an existing GroupPolicy folder) or -SourceISO (a Windows installation .iso)."
        Read-Host "Press ENTER to exit."
        exit
    }

    if (-not (Test-Path -Path $EffectiveSourcePath)) {
        Write-Log "No GroupPolicy folder found at '$EffectiveSourcePath'." -Color Yellow
        Write-Log "This is expected for stock/unmodified Windows installation media -" -Color Yellow
        Write-Log "a fresh install has no local GPO configured (no Registry.pol files)." -Color Yellow
        if ($CleanupMountInfo) { Dismount-GPOSourceISO -MountInfo $CleanupMountInfo }
        Read-Host "Press ENTER to exit."
        exit
    }

    Write-Log "Building local policy definition index from ADMX templates..." -Color Cyan
    $Index = Get-LocalPolicyDefinitionIndex
    Write-Log "Index built: $($Index.Count) known key/value combinations on this system." -Color Cyan

    $Pairs = @(
        @{ Name = "Machine"; Source = (Join-Path $EffectiveSourcePath "Machine\Registry.pol"); Target = (Join-Path $GPRoot "Machine\Registry.pol") },
        @{ Name = "User";    Source = (Join-Path $EffectiveSourcePath "User\Registry.pol");    Target = (Join-Path $GPRoot "User\Registry.pol") }
    )

    foreach ($Pair in $Pairs) {
        Write-Log "`n--- Processing $($Pair.Name) policies ---" -Color White

        if (-not (Test-Path -Path $Pair.Source)) {
            Write-Log "  No $($Pair.Name)\Registry.pol found at source. Skipping." -Color Yellow
            continue
        }

        $SourceEntries = Read-PolFile -Path $Pair.Source
        $TargetEntries = Read-PolFile -Path $Pair.Target

        $Accepted = @()
        $RejectedCount = 0

        foreach ($Entry in $SourceEntries) {
            if (Test-PolicyEntryCompatibility -Entry $Entry -Index $Index) {
                $Accepted += $Entry
                Write-Log "  ACCEPTED : $($Entry.Key) \ $($Entry.ValueName)" -Color Green
            }
            else {
                $RejectedCount++
                Write-Log "  REJECTED : $($Entry.Key) \ $($Entry.ValueName) (no matching policy on this system)" -Color Red
            }
        }

        Write-Log "  Summary: $($Accepted.Count) compatible, $RejectedCount incompatible." -Color Cyan

        if ($Accepted.Count -eq 0) {
            Write-Log "  Nothing compatible to merge for $($Pair.Name)." -Color Yellow
            continue
        }

        $MergedEntries = @($TargetEntries)
        foreach ($NewEntry in $Accepted) {
            $ExistingIndex = -1
            for ($i = 0; $i -lt $MergedEntries.Count; $i++) {
                if ($MergedEntries[$i].Key -eq $NewEntry.Key -and $MergedEntries[$i].ValueName -eq $NewEntry.ValueName) {
                    $ExistingIndex = $i
                    break
                }
            }
            if ($ExistingIndex -ge 0) {
                $MergedEntries[$ExistingIndex] = $NewEntry
            }
            else {
                $MergedEntries += $NewEntry
            }
        }

        if ($DryRun) {
            Write-Log "  DRY RUN: would write $($MergedEntries.Count) total entries to '$($Pair.Target)'." -Color Magenta
        }
        else {
            $TargetParent = Split-Path -Path $Pair.Target -Parent
            if (-not (Test-Path -Path $TargetParent)) {
                New-Item -Path $TargetParent -ItemType Directory -Force | Out-Null
            }
            Write-PolFile -Path $Pair.Target -Entries $MergedEntries
            Write-Log "  Wrote $($MergedEntries.Count) total entries to '$($Pair.Target)'." -Color Green
        }
    }

    if ($CleanupMountInfo) {
        Dismount-GPOSourceISO -MountInfo $CleanupMountInfo
    }

    if (-not $DryRun) {
        Write-Log "`nRunning gpupdate /force to apply merged policies..." -Color Cyan
        Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -WindowStyle Hidden
    }
}

# ---------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------
Initialize-Logging -Folder $LogFolder

if ($DryRun) {
    Write-Log "===== DRY RUN MODE: no changes will actually be made =====" -Color Magenta
}
if ($script:LogFilePath) {
    Write-Log "Log file: $script:LogFilePath" -Color DarkGray
}

if (-not $DryRun) {
    $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        Write-Warning "ERROR: This script must be run as ADMINISTRATOR for the '$Action' action."
        Write-Host "Please reopen PowerShell as Administrator and try again." -ForegroundColor Cyan
        Write-Host "Tip: use -DryRun to preview the operation without elevation." -ForegroundColor Cyan
        Read-Host "Press ENTER to exit."
        exit
    }
}

$GPRoot = Join-Path $env:SystemRoot "System32\GroupPolicy"
$GPUsersRoot = Join-Path $env:SystemRoot "System32\GroupPolicyUsers"

switch ($Action) {
    "Backup"  { Invoke-GPOBackup -TargetBackupFolder $BackupFolder | Out-Null }
    "Restore" { Invoke-GPORestore -SourceBackupFolder $BackupFolder }
    "Reset"   { Invoke-GPOReset }
    "Import"  { Invoke-GPOImport }
}

if ($script:LogFilePath) {
    Write-Log "`nFull log saved to: $script:LogFilePath" -Color DarkGray
}

Read-Host "`nPress ENTER to exit."
Güle güle kullanın...
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

Ustalık Eserlerimin Gerçek Komutsal Kullanımı...

Mesaj gönderen TRWE_2012 »

Merhabalar

Bu forumsal alt mesajıma espirili bir başlık atarak başlayalım dedim..

Örnek Gerçek Dosya Yolları :

Yedekleme Yapılacak Dizin Yolu :

D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI

Örnek ISO Dosyası Yolu :

E:\ANA MASAÜSTÜ UYGULAMALARI 2023\1.DİZİN_İŞLETİM SİSTEMLERİ\1.MİCROSOFT WİNDOWS İŞLETİM SİSTEMLERİ\1.İŞLETİM SİSTEMİ ISO KALIPLARI\Windows 11 24H2 x64\Windows11.24H2 x64.iso

Artı, betiklerin isimlerini ben kendime göre değiştirdim, sırf aradaki farkı görünüz diye...

D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v1.0.ps1
D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v2.0.ps1

Şimdi bunlara göre aşağıdaki komut kalıplarını da oluşturalım...

1. Backup

Kod: Tümünü seç

& "D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v1.0.ps1" -Action Backup -BackupFolder "D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI"
NOT:

Başa & (call operator) eklenmesi zorunludur. Çünkü yol tırnak içinde bir string olarak yazıldığında PowerShell onu otomatik olarak çalıştırılabilir komut saymaz; & ile "bu string'i bir komut olarak çalıştır" demiş oluyorsunuz. .\dosya.ps1 şeklinde göreli yol kullanırken buna gerek yoktur, ama tam yol + boşluklu isim + tırnak kombinasyonunda & gereklidir.

Çıktı:

Kod: Tümünü seç

Backing up Group Policy folders to 'D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI'...
  Copied: GroupPolicy
  Copied: GroupPolicyUsers
  Backup complete: D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI

EKRAN GÖRÜNTÜSÜ : ( Sistem : Canlı Sistem : Windows11.24H2.R7019.x64 Home TR)
Resim
2. Restore (Yedek Klasöründen GERİ YÜKLEME)

Komut Yapısı :

Kod: Tümünü seç

& "D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v1.0.ps1" -Action Restore -BackupFolder "D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI\GroupPolicy_Backup_20260822_150938"
Çıktı :

Kod: Tümünü seç

Restoring Group Policy folders from 'D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI'...
  Restored: GroupPolicy
  Restored: GroupPolicyUsers
  Running gpupdate /force to apply restored policies...
  Restore complete.

EKRAN GÖRÜNTÜSÜ : ( Sistem : Canlı Sistem : Windows11.24H2.R7019.x64 Home TR)
Resim
3. Reset [Fabrika ayarlarınına SIFIRLAMA]

Komut Yapısı :

Kod: Tümünü seç

& "D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v2.0.ps1" -Action Reset -BackupFolder "D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI"
Çıktı :

Kod: Tümünü seç

Resetting local Group Policy to its default (unconfigured) state...
NOTE: this mirrors the RD /S /Q technique normally run from Windows
Recovery / installation media. Running it from a live session works
in most cases. If a file is reported as locked, boot into WinRE /
Setup and run these two commands instead:
  RD /S /Q "C:\Windows\System32\GroupPolicy"
  RD /S /Q "C:\Windows\System32\GroupPolicyUsers"
Backing up Group Policy folders to 'D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI'...
  Copied: GroupPolicy
  Copied: GroupPolicyUsers
  Backup complete: D:\YEDEKLENMİŞ GRUP POLİTİKA AYARLARI
  Removed: GroupPolicy
  Removed: GroupPolicyUsers
  Running gpupdate /force to apply the reset...
  Reset complete. Local Group Policy is back to its default state.

NOT : Ekran görüntüsü vermeyeceğim.Çünkü sıfırlamak istemiyorum ayarları..

4. Import — ISO'dan (v2.0), önce DryRun

Komut Yapısı :

Kod: Tümünü seç

& "D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v2.0.ps1" -Action Import -SourceISO "E:\ANA MASAÜSTÜ UYGULAMALARI 2023\1.DİZİN_İŞLETİM SİSTEMLERİ\1.MİCROSOFT WİNDOWS İŞLETİM SİSTEMLERİ\1.İŞLETİM SİSTEMİ ISO KALIPLARI\Windows 11 24H2 x64\Windows11.24H2 x64.iso" -DryRun
Çıktı:

Kod: Tümünü seç

===== DRY RUN MODE: no changes will actually be made =====
Building local policy definition index from ADMX templates...
Index built: 3120 known key/value combinations on this system.
No GroupPolicy folder found at 'C:\...\Mount\...\Windows\System32\GroupPolicy'.
This is expected for stock/unmodified Windows installation media -
a fresh install has no local GPO configured (no Registry.pol files).
EKRAN GÖRÜNTÜSÜ : ( Sistem : Canlı Sistem : Windows11.24H2.R7019.x64 Home TR)
Resim
Açıklama :

Betik tam beklendiği gibi çalışmıştır ve size bir seçim yaptırmak için durmuş durumdadır. Şu an ISO içinde birden fazla Windows 11 edition (Pro / Home / Home Single Language) bulunmuştur.(çünkü bu ISO @KayseriliFatih tarafından yapılan bir modifiyedir.), betik hangisini mount edeceğini bilemediği için index listesini gösterip beklemek konumuna geçmiştir.Orjinal ISO'da bu durum olmayacaktır.

Bu aşamadan sonra yapılması gerekenler :

1.Önce Press ENTER to exit. yazan yerde Enter'a basıp script'ten çıkın , mount edilen ISO (F:\) otomatik olarak sistemden ayrılmıştır, bu normal, betik zaten bu noktada exit ile sonlanıyor.

2.Ardından uygun Index numarasını belirleyip -SourceWimIndex parametresiyle yeniden çalıştırın.

Yani yeni komut kalıbımız şöyle olacak...

Sistemimde kurulu olan Windows 11 24H2 Home x64 TR (Lenovo BIOS lisanslı), yani listede Index 2: Windows 11 Home bana uygun olanı... Import işleminin mantığı zaten "bu sistemde tanımlı olan ADMX politikalarıyla eşleşenleri al" olduğu için, kaynak ISO'daki edition'ın da mevcut sistemimle aynı/uyumlu olması mantıklıdır.

Yeniden çalıştırma komutu:

Kod: Tümünü seç

& "D:\MİCROSOFT BETİKLERİ 2024\Kullanılmayacak Betikler\Manage LocalGroupPolicy v2.0.ps1" -Action Import -SourceISO "E:\ANA MASAÜSTÜ UYGULAMALARI 2023\1.DİZİN_İŞLETİM SİSTEMLERİ\1.MİCROSOFT WİNDOWS İŞLETİM SİSTEMLERİ\1.İŞLETİM SİSTEMİ ISO KALIPLARI\Windows 11 24H2 x64\Windows11.24H2 x64.iso" -SourceWimIndex 2 -DryRun
Beklenen çıktı:

Kod: Tümünü seç

===== DRY RUN MODE: no changes will actually be made =====
Mounting ISO: E:\...\Windows11.24H2 x64.iso
  ISO mounted at F:\
  Found image: F:\sources\install.wim
  Mounting Windows 11 Home (Index 2) read-only via DISM...
  Image mounted at C:\...\Mount\...
Building local policy definition index from ADMX templates...
Index built: 3120 known key/value combinations on this system.
No GroupPolicy folder found at 'C:\...\Mount\...\Windows\System32\GroupPolicy'.
This is expected for stock/unmodified Windows installation media -
a fresh install has no local GPO configured (no Registry.pol files).
EKRAN GÖRÜNTÜSÜ : ( Sistem : Canlı Sistem : Windows11.24H2.R7019.x64 Home TR)
Resim
Resim
Resim
NOT :

Daha önce de belirttiğim gibi, bu orijinal/stock bir kurulum ISO'su olduğu için GroupPolicy klasörünün hiç bulunamaması yüksek ihtimal , bu bir hata değildir, script'in kendi yorumunda da açıkça belirtilen beklenen bir sonuçtur.
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

UserAccountManager.ps1 Betiğini Geliştirmeye Başladım (Hayallerimindeki betik tasarımlarından biri)

Mesaj gönderen TRWE_2012 »

Merhabalar

Ekran görüntüsü :
Resim
Gerçek canlı sistem üzerinde denemeye başladım.(uçurumun kıyısında gezen TRWE_2012) . TEST_USER isimli bir hesap üzerinden gideceğim....Testlerim olumlu olursa burada nihai sürümün kodlarını vereceğim...
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15611
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2709 kez
Teşekkür edildi: 5626 kez

Re: UserAccountManager.ps1 Betiğini Geliştirmeye Başladım (Hayallerimindeki betik tasarımlarından biri)

Mesaj gönderen TRWE_2012 »

TRWE_2012 yazdı: 23 Ağu 2026, 21:32 Merhabalar

Ekran görüntüsü :
Resim
Gerçek canlı sistem üzerinde denemeye başladım.(uçurumun kıyısında gezen TRWE_2012) . TEST_USER isimli bir hesap üzerinden gideceğim....Testlerim olumlu olursa burada nihai sürümün kodlarını vereceğim...
Evet canlı sistem'de test işlemleri bitirildi.(Sabah 08:30'da) Akşam Makalesiyle birlikte burada yayınlayacağım.Yok böyle bir betik türünün ilk ve son örneği ve de sadece burada (sordum forum'da) var.Artık Win11-10'da arayüzden uğraşmadan bir saniye hesap oluşturup silebilirsiniz/resetleyebilirsiniz/sıfırlayabilirsiniz./şifre değiştirebilirsiniz/hesabı pasif/aktifleştirebilirsiniz.Çok uğraştım kendi sistemimde risk aldım ama güzel oldu.
Kullanıcı avatarı
velociraptor
Yottabyte4
Yottabyte4
Mesajlar: 54872
Kayıt: 14 Mar 2006, 02:33
cinsiyet: Erkek
Teşekkür etti: 21123 kez
Teşekkür edildi: 12485 kez

Re: Deneme Altında Olan Betiklerim 2026

Mesaj gönderen velociraptor »

Resim

Alternatif olarak arayüz ile aynı işlemleri yapabilirsiniz :
https://www.sordum.net/47558/windows-ku ... kla-yapin/

Download :
https://disk.yandex.com.tr/d/dRvjRIHFYfI8BQ
Cevapla

“Programlama ve Script dilleri” sayfasına dön