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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 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: 21124 kez
Teşekkür edildi: 12486 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
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15612
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2710 kez
Teşekkür edildi: 5626 kez

UserAccountManager.ps1 [Kararlı Sürüm]

Mesaj gönderen TRWE_2012 »

TRWE_2012 yazdı: 24 Ağu 2026, 09:29
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.
KONU ANLATIMI :
Resim
EKRAN GÖRÜNTÜLERİ :
Resim
Resim
Resim
Resim
Resim
Resim
Resim
Resim
Resim
KOD İÇERİKLERİ :

İngilizce ( UserAccountManager_v4.0.ps1 )

Kod: Tümünü seç

# Windows Local User Account Management Script
# Must be run as Administrator

# Administrator check
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

if (-not $isAdmin) {
    Write-Host "ERROR: This script must be run as Administrator!" -ForegroundColor Red
    Write-Host "Right-click PowerShell and choose 'Run as Administrator'." -ForegroundColor Yellow
    Read-Host "`nPress ENTER to exit."
    exit
}

function Show-Banner {
    Clear-Host
    Write-Host "*******************************************************************************" -ForegroundColor Cyan
    Write-Host "              WELCOME TO THE WINDOWS USER ACCOUNT MANAGER                     " -ForegroundColor Green
    Write-Host "*******************************************************************************" -ForegroundColor Cyan
    Write-Host ""
}

function Show-Menu {
    Write-Host "What would you like to do (press the matching key):" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "1 = View existing Windows accounts" -ForegroundColor White
    Write-Host "2 = Manage an existing Windows account" -ForegroundColor White
    Write-Host "    (change password / view info / enable / disable / permanently delete)" -ForegroundColor Gray
    Write-Host "3 = Create a new Windows account from scratch" -ForegroundColor White
    Write-Host "4 = Reset an existing Windows account (from the registry)" -ForegroundColor White
    Write-Host "0 = Exit" -ForegroundColor Red
    Write-Host ""
}

function Show-Users {
    Write-Host "`n========== EXISTING WINDOWS ACCOUNTS ==========" -ForegroundColor Cyan
    $users = Get-LocalUser | Select-Object Name, Enabled, Description, LastLogon
    $users | Format-Table -AutoSize
    Write-Host "Total number of accounts: $($users.Count)" -ForegroundColor Green
}

function Manage-Users {
    Show-Banner
    Write-Host "========== ACCOUNT MANAGEMENT ==========" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "1 = Change password" -ForegroundColor White
    Write-Host "2 = View account details" -ForegroundColor White
    Write-Host "3 = Enable account" -ForegroundColor White
    Write-Host "4 = Disable account" -ForegroundColor White
    Write-Host "5 = Permanently delete account" -ForegroundColor White
    Write-Host "0 = Back to main menu" -ForegroundColor Yellow
    Write-Host ""

    $choice = Read-Host "Your choice"

    switch ($choice) {
        "1" { Change-Password }
        "2" { Show-UserInfo }
        "3" { Enable-UserAccountFn }
        "4" { Disable-UserAccountFn }
        "5" { Remove-UserAccountFn }
        "0" { return }
        default {
            Write-Host "Invalid choice!" -ForegroundColor Red
            Start-Sleep -Seconds 2
            Manage-Users
        }
    }
}

function Change-Password {
    Write-Host "`n========== CHANGE PASSWORD ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nUsername whose password you want to change"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        $password = Read-Host "New password" -AsSecureString
        $user | Set-LocalUser -Password $password
        Write-Host "`nPassword for '$username' was changed successfully!" -ForegroundColor Green
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

function Show-UserInfo {
    Write-Host "`n========== ACCOUNT DETAILS ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nUsername whose details you want to view"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        Write-Host "`n--- Details for '$username' ---" -ForegroundColor Yellow
        $user | Format-List Name, FullName, Description, Enabled, LastLogon, PasswordLastSet, PasswordExpires, UserMayChangePassword, PasswordRequired, SID

        # Show group memberships
        Write-Host "`n--- Group Memberships ---" -ForegroundColor Yellow
        $groups = Get-LocalGroup | Where-Object {
            (Get-LocalGroupMember -Group $_.Name -ErrorAction SilentlyContinue).Name -contains "$env:COMPUTERNAME\$username"
        }
        $groups | Format-Table Name, Description -AutoSize
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

function Enable-UserAccountFn {
    Write-Host "`n========== ENABLE ACCOUNT ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nUsername you want to enable"

    try {
        Enable-LocalUser -Name $username -ErrorAction Stop
        Write-Host "`nAccount '$username' was enabled successfully!" -ForegroundColor Green
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

function Disable-UserAccountFn {
    Write-Host "`n========== DISABLE ACCOUNT ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nUsername you want to disable"

    try {
        Disable-LocalUser -Name $username -ErrorAction Stop
        Write-Host "`nAccount '$username' was disabled successfully!" -ForegroundColor Green
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

function Remove-UserAccountFn {
    Write-Host "`n========== PERMANENTLY DELETE ACCOUNT ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nUsername you want to permanently delete"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        $userSID = $user.SID.Value

        # Find the user's profile path
        $profilePath = $null
        $regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$userSID"

        if (Test-Path $regPath) {
            $profileInfo = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
            $profilePath = $profileInfo.ProfileImagePath
        }

        # Fall back to the default path if not found in the registry
        if (-not $profilePath) {
            $profilePath = "C:\Users\$username"
        }

        # Show the details before deleting
        Write-Host "`n========== DATA TO BE DELETED ==========" -ForegroundColor Yellow
        Write-Host "Username         : $username" -ForegroundColor White
        Write-Host "Full name        : $($user.FullName)" -ForegroundColor White
        Write-Host "Account status   : $($user.Enabled)" -ForegroundColor White
        Write-Host "Last logon       : $($user.LastLogon)" -ForegroundColor White
        Write-Host "Profile folder   : $profilePath" -ForegroundColor White
        Write-Host "SID              : $userSID" -ForegroundColor White

        # Check whether the profile folder exists
        $profileExists = Test-Path $profilePath
        if ($profileExists) {
            try {
                $folderSize = (Get-ChildItem -Path $profilePath -Recurse -Force -ErrorAction SilentlyContinue |
                               Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
                Write-Host "Folder size      : $([math]::Round($folderSize, 2)) MB" -ForegroundColor White
            }
            catch {
                Write-Host "Folder size      : Could not be calculated" -ForegroundColor Gray
            }
        }
        else {
            Write-Host "Profile folder   : Not found (may already be deleted)" -ForegroundColor Gray
        }

        Write-Host "`n========================================" -ForegroundColor Yellow
        Write-Host "WARNING: THIS ACTION CANNOT BE UNDONE!" -ForegroundColor Red
        Write-Host "========================================" -ForegroundColor Yellow
        Write-Host "`nThe following will be deleted:" -ForegroundColor Red
        Write-Host "  - The user account" -ForegroundColor White
        Write-Host "  - The user profile folder ($profilePath)" -ForegroundColor White
        Write-Host "  - All user files (Documents, Desktop, etc.)" -ForegroundColor White
        Write-Host "  - Registry entries" -ForegroundColor White

        Write-Host ""
        $confirm1 = Read-Host "Are you SURE you want to continue? (YES/NO)"

        if ($confirm1 -eq "YES") {
            Write-Host "`nFINAL WARNING: All data will be permanently deleted!" -ForegroundColor Red
            $confirm2 = Read-Host "Type the username again to confirm"

            if ($confirm2 -eq $username) {

                Write-Host "`n========== DELETION IN PROGRESS ==========" -ForegroundColor Yellow

                # 1. Log the user off first, if currently signed in (active or disconnected)
                Write-Host "`n[1/5] Checking for active sessions for this user..." -ForegroundColor Cyan
                try {
                    $sessionLines = quser 2>$null | Select-String $username
                    if ($sessionLines) {
                        foreach ($line in $sessionLines) {
                            # logoff.exe requires a SESSION ID or SESSIONNAME, not a username.
                            # Passing a username directly fails silently and leaves the
                            # session (and its loaded registry hive) running - this happens
                            # especially with Fast User Switching, where a prior session
                            # shows up as "Disc" (disconnected) with an empty SESSIONNAME
                            # column, shifting the field positions in quser's output.
                            $tokens = ($line.ToString().Trim() -replace '^>', '').Trim() -split '\s+'
                            $sessionId = $tokens | Where-Object { $_ -match '^\d+$' } | Select-Object -First 1

                            if ($sessionId) {
                                Write-Host "  -> Session found (ID: $sessionId), logging off..." -ForegroundColor Yellow
                                logoff $sessionId 2>$null
                                Start-Sleep -Seconds 2
                            }
                        }
                    }
                    else {
                        Write-Host "  -> No active or disconnected session" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "  -> Session check skipped" -ForegroundColor Gray
                }

                # 1b. quser only reports interactive/RDP sessions. A process can be running
                # as this user without any of those (runas, a scheduled task, a service, a
                # background process launched from another admin account). Any such process
                # keeps the registry hive load-count above zero and blocks folder deletion
                # later on, so it must be found and stopped independently of the session check.
                Write-Host "`n[2/5] Checking for background processes running as this user..." -ForegroundColor Cyan
                try {
                    $ownedProcesses = @()
                    $allProcesses = Get-CimInstance -ClassName Win32_Process -ErrorAction Stop
                    foreach ($proc in $allProcesses) {
                        try {
                            $owner = Invoke-CimMethod -InputObject $proc -MethodName GetOwner -ErrorAction Stop
                            if ($owner.User -eq $username) {
                                $ownedProcesses += [PSCustomObject]@{
                                    ProcessId = $proc.ProcessId
                                    Name      = $proc.Name
                                }
                            }
                        }
                        catch {
                            # Some system processes do not expose an owner; skip them
                        }
                    }

                    if ($ownedProcesses.Count -gt 0) {
                        Write-Host "  -> Found $($ownedProcesses.Count) process(es) running as '$username':" -ForegroundColor Yellow
                        $ownedProcesses | Format-Table ProcessId, Name -AutoSize

                        $confirmKill = Read-Host "  Terminate these processes now? (YES/NO)"
                        if ($confirmKill -eq "YES") {
                            foreach ($p in $ownedProcesses) {
                                try {
                                    Stop-Process -Id $p.ProcessId -Force -ErrorAction Stop
                                    Write-Host "  [OK] Terminated PID $($p.ProcessId) ($($p.Name))" -ForegroundColor Green
                                }
                                catch {
                                    Write-Host "  [WARNING] Could not terminate PID $($p.ProcessId) ($($p.Name)) - $($_.Exception.Message)" -ForegroundColor Yellow
                                }
                            }
                            Start-Sleep -Seconds 2
                        }
                        else {
                            Write-Host "  -> Skipped. These processes may keep the registry hive locked, which can cause the profile folder removal in step 5 to leave a residual folder." -ForegroundColor Yellow
                        }
                    }
                    else {
                        Write-Host "  -> No background processes found for this user" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "  -> Process check skipped: $($_.Exception.Message)" -ForegroundColor Gray
                }

                # 2. Remove the user account
                Write-Host "`n[3/5] Removing the user account..." -ForegroundColor Cyan
                try {
                    Remove-LocalUser -Name $username -ErrorAction Stop
                    Write-Host "  [OK] User account removed successfully" -ForegroundColor Green
                }
                catch {
                    Write-Host "  [ERROR] Could not remove the user account - $($_.Exception.Message)" -ForegroundColor Red
                    Read-Host "`nPress ENTER to exit."
                    return
                }

                # 3. Remove the registry profile entry
                Write-Host "`n[4/5] Removing the registry profile entry..." -ForegroundColor Cyan
                try {
                    if (Test-Path $regPath) {
                        Remove-Item -Path $regPath -Recurse -Force -ErrorAction Stop
                        Write-Host "  [OK] Registry profile entry removed" -ForegroundColor Green
                    }
                    else {
                        Write-Host "  -> No registry profile entry found" -ForegroundColor Gray
                    }
                }
                catch {
                    Write-Host "  [WARNING] Could not clean up the registry - $($_.Exception.Message)" -ForegroundColor Yellow
                }

                # 3b. If the account's registry hive is still mounted under HKEY_USERS,
                # NTUSER.DAT stays locked at the OS level regardless of file permissions.
                # Explicitly unload it before touching the profile folder.
                Write-Host "`n[4b/5] Checking for a mounted registry hive..." -ForegroundColor Cyan
                try {
                    $hiveMounted = Test-Path "Registry::HKEY_USERS\$userSID"
                    if ($hiveMounted) {
                        Write-Host "  -> Hive is still mounted, unloading..." -ForegroundColor Yellow
                        [gc]::Collect()
                        [gc]::WaitForPendingFinalizers()
                        reg unload "HKU\$userSID" 2>$null | Out-Null
                        Start-Sleep -Seconds 1
                    }
                    else {
                        Write-Host "  -> No mounted hive found" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "  -> Hive unload skipped: $($_.Exception.Message)" -ForegroundColor Gray
                }

                # 4. Remove the user profile folder
                Write-Host "`n[5/5] Removing the user profile folder..." -ForegroundColor Cyan
                if ($profileExists) {
                    try {
                        # Short delay so files are not still in use
                        Start-Sleep -Seconds 2

                        Remove-Item -Path $profilePath -Recurse -Force -ErrorAction Stop
                    }
                    catch {
                        Write-Host "  [WARNING] Some files could not be removed" -ForegroundColor Yellow
                        Write-Host "  Error: $($_.Exception.Message)" -ForegroundColor Red

                        # Try an alternative removal method
                        Write-Host "`n  -> Trying an alternative method..." -ForegroundColor Yellow
                        try {
                            # Take ownership and grant permissions before deleting
                            takeown /f $profilePath /r /d y 2>$null | Out-Null
                            icacls $profilePath /grant administrators:F /t 2>$null | Out-Null
                            Remove-Item -Path $profilePath -Recurse -Force -ErrorAction Stop
                        }
                        catch {
                            Write-Host "  [ERROR] Removal attempt raised an error - $($_.Exception.Message)" -ForegroundColor Red
                        }
                    }

                    # Do not trust a silent "success" - verify the folder is actually gone.
                    # A locked hive can cause Remove-Item to report no error while the OS
                    # has not fully released the folder yet, so re-check with brief retries.
                    $stillThere = $true
                    for ($i = 0; $i -lt 3; $i++) {
                        if (-not (Test-Path $profilePath)) {
                            $stillThere = $false
                            break
                        }
                        Start-Sleep -Seconds 1
                    }

                    if (-not $stillThere) {
                        Write-Host "  [OK] User profile folder removed and verified: $profilePath" -ForegroundColor Green
                    }
                    else {
                        Write-Host "  [WARNING] Folder still exists after removal attempt and verification wait." -ForegroundColor Yellow
                        Write-Host "  This usually means a registry hive or another process still holds a lock." -ForegroundColor Yellow
                        Write-Host "  [ERROR] The folder could not be fully removed. Manual deletion may be required:" -ForegroundColor Red
                        Write-Host "    $profilePath" -ForegroundColor White
                        Write-Host "`n  To delete it manually:" -ForegroundColor Yellow
                        Write-Host "    1. Open File Explorer as Administrator" -ForegroundColor White
                        Write-Host "    2. Go to $profilePath" -ForegroundColor White
                        Write-Host "    3. Right-click and choose Delete" -ForegroundColor White
                    }
                }
                else {
                    Write-Host "  -> User profile folder not found (already deleted)" -ForegroundColor Gray
                }

                # Summary
                Write-Host "`n========================================" -ForegroundColor Green
                Write-Host "          DELETION COMPLETE" -ForegroundColor Green
                Write-Host "========================================" -ForegroundColor Green
                Write-Host "`nUser '$username' has been fully removed from the system!" -ForegroundColor Green

            }
            else {
                Write-Host "`nUsername did not match. Operation cancelled." -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "`nOperation cancelled." -ForegroundColor Yellow
        }
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

function Create-NewUser {
    Write-Host "`n========== CREATE NEW ACCOUNT ==========" -ForegroundColor Cyan

    $username = Read-Host "New username"
    $fullname = Read-Host "Full name (optional)"
    $description = Read-Host "Description (optional)"
    $password = Read-Host "Password" -AsSecureString

    try {
        $params = @{
            Name                 = $username
            Password             = $password
            PasswordNeverExpires = $true
        }

        if ($fullname) { $params.Add("FullName", $fullname) }
        if ($description) { $params.Add("Description", $description) }

        New-LocalUser @params -ErrorAction Stop
        Write-Host "`nAccount '$username' was created successfully!" -ForegroundColor Green

        # Add to a group
        $addToGroup = Read-Host "`nDo you want to add this account to a group? (Y/N)"
        if ($addToGroup -eq "Y" -or $addToGroup -eq "y") {
            Write-Host "`nExisting Groups:" -ForegroundColor Yellow
            Get-LocalGroup | Format-Table Name, Description -AutoSize

            $groupName = Read-Host "`nGroup name (e.g. Administrators, Users)"
            try {
                Add-LocalGroupMember -Group $groupName -Member $username -ErrorAction Stop
                Write-Host "`n'$username' was added to the '$groupName' group!" -ForegroundColor Green
            }
            catch {
                Write-Host "`nERROR: Could not add to the group - $($_.Exception.Message)" -ForegroundColor Red
            }
        }
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

function Reset-UserFromRegistry {
    Write-Host "`n========== RESET ACCOUNT FROM REGISTRY ==========" -ForegroundColor Cyan
    Write-Host "WARNING: This is an advanced operation, use it with caution!" -ForegroundColor Red
    Write-Host ""

    Show-Users

    $username = Read-Host "`nUsername you want to reset"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        $sid = $user.SID.Value

        Write-Host "`nUser SID: $sid" -ForegroundColor Yellow

        $confirm = Read-Host "`nDo you want to reset this user's registry profile entry? (Y/N)"

        if ($confirm -eq "Y" -or $confirm -eq "y") {
            $profilePath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid"

            if (Test-Path $profilePath) {
                Write-Host "`nProfile registry key found: $profilePath" -ForegroundColor Green

                $profileInfo = Get-ItemProperty -Path $profilePath
                Write-Host "`nCurrent profile folder: $($profileInfo.ProfileImagePath)" -ForegroundColor Yellow

                $deleteProfile = Read-Host "`nDo you want to delete this registry profile entry? (Y/N)"
                if ($deleteProfile -eq "Y" -or $deleteProfile -eq "y") {
                    Remove-Item -Path $profilePath -Recurse -Force
                    Write-Host "`nRegistry profile entry deleted!" -ForegroundColor Green
                    Write-Host "NOTE: The user will get a fresh profile on next logon." -ForegroundColor Yellow
                }
            }
            else {
                Write-Host "`nNo registry profile entry found for this user." -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "`nOperation cancelled." -ForegroundColor Yellow
        }
    }
    catch {
        Write-Host "`nERROR: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nPress ENTER to exit."
}

# Main program loop
do {
    Show-Banner
    Show-Menu

    $choice = Read-Host "Your choice"

    switch ($choice) {
        "1" {
            Show-Banner
            Show-Users
            Read-Host "`nPress ENTER to exit."
        }
        "2" {
            Manage-Users
        }
        "3" {
            Show-Banner
            Create-NewUser
        }
        "4" {
            Show-Banner
            Reset-UserFromRegistry
        }
        "0" {
            Write-Host "`nClosing the program..." -ForegroundColor Yellow
            Start-Sleep -Seconds 1
            exit
        }
        default {
            Write-Host "`nInvalid choice! Please enter a number from 0 to 4." -ForegroundColor Red
            Start-Sleep -Seconds 2
        }
    }

} while ($true)
TÜRKÇE ( UserAccountManager_v4.0_TR.ps1) :

Kod: Tümünü seç

# Windows Yerel Kullanıcı Hesabı Yönetim Betiği
# Yönetici olarak çalıştırılmalıdır

# Konsol çıktı encoding'ini UTF-8'e sabitle (PS 5.1 / PS7 ve farklı kod sayfaları arasındaki
# Türkçe karakter bozulmalarını önlemek için; dosya UTF-8 BOM ile kaydedilmiştir)
try {
    [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
    $OutputEncoding = [System.Text.Encoding]::UTF8
    chcp 65001 > $null
}
catch {
    # Konsol encoding ayarı başarısız olursa sessizce devam et
}

# Yönetici kontrolü
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

if (-not $isAdmin) {
    Write-Host "HATA: Bu betik yönetici olarak çalıştırılmalıdır!" -ForegroundColor Red
    Write-Host "PowerShell'i sağ tıklayıp 'Yönetici olarak çalıştır' seçeneğini kullanın." -ForegroundColor Yellow
    Read-Host "`nÇıkmak için ENTER tuşuna basın."
    exit
}

function Show-Banner {
    Clear-Host
    Write-Host "*******************************************************************************" -ForegroundColor Cyan
    Write-Host "        WINDOWS KULLANICI HESAP YÖNETİCİSİNE HOŞ GELDİNİZ                     " -ForegroundColor Green
    Write-Host "*******************************************************************************" -ForegroundColor Cyan
    Write-Host ""
}

function Show-Menu {
    Write-Host "Bu ekranda yapabilecekleriniz (klavyeden ilgili tuşa basın):" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "1 = Mevcut Windows hesaplarını görüntüle" -ForegroundColor White
    Write-Host "2 = Mevcut bir Windows hesabıyla ilgili işlemler yap" -ForegroundColor White
    Write-Host "    (şifre değiştirme / hesap bilgilerini görüntüleme / etkinleştirme / pasifleştirme / kalıcı silme)" -ForegroundColor Gray
    Write-Host "3 = Sıfırdan yeni bir Windows hesabı oluştur" -ForegroundColor White
    Write-Host "4 = Mevcut bir Windows hesabını kayıt defterinden sıfırla" -ForegroundColor White
    Write-Host "0 = Çıkış" -ForegroundColor Red
    Write-Host ""
}

function Show-Users {
    Write-Host "`n========== MEVCUT WINDOWS HESAPLARI ==========" -ForegroundColor Cyan
    $users = Get-LocalUser | Select-Object Name, Enabled, Description, LastLogon
    $users | Format-Table -AutoSize
    Write-Host "Toplam hesap sayısı: $($users.Count)" -ForegroundColor Green
}

function Manage-Users {
    Show-Banner
    Write-Host "========== HESAP YÖNETİMİ ==========" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "1 = Şifre değiştir" -ForegroundColor White
    Write-Host "2 = Hesap bilgilerini görüntüle" -ForegroundColor White
    Write-Host "3 = Hesabı etkinleştir" -ForegroundColor White
    Write-Host "4 = Hesabı pasifleştir" -ForegroundColor White
    Write-Host "5 = Hesabı kalıcı olarak sil" -ForegroundColor White
    Write-Host "0 = Ana menüye dön" -ForegroundColor Yellow
    Write-Host ""

    $choice = Read-Host "Seçiminiz"

    switch ($choice) {
        "1" { Change-Password }
        "2" { Show-UserInfo }
        "3" { Enable-UserAccountFn }
        "4" { Disable-UserAccountFn }
        "5" { Remove-UserAccountFn }
        "0" { return }
        default {
            Write-Host "Geçersiz seçim!" -ForegroundColor Red
            Start-Sleep -Seconds 2
            Manage-Users
        }
    }
}

function Change-Password {
    Write-Host "`n========== ŞİFRE DEĞİŞTİRME ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nŞifresini değiştirmek istediğiniz kullanıcı adı"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        $password = Read-Host "Yeni şifre" -AsSecureString
        $user | Set-LocalUser -Password $password
        Write-Host "`n'$username' kullanıcısının şifresi başarıyla değiştirildi!" -ForegroundColor Green
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

function Show-UserInfo {
    Write-Host "`n========== HESAP BİLGİLERİ ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nBilgilerini görüntülemek istediğiniz kullanıcı adı"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        Write-Host "`n--- '$username' Detaylı Bilgileri ---" -ForegroundColor Yellow
        $user | Format-List Name, FullName, Description, Enabled, LastLogon, PasswordLastSet, PasswordExpires, UserMayChangePassword, PasswordRequired, SID

        # Grup üyeliklerini göster
        Write-Host "`n--- Üye Olunan Gruplar ---" -ForegroundColor Yellow
        $groups = Get-LocalGroup | Where-Object {
            (Get-LocalGroupMember -Group $_.Name -ErrorAction SilentlyContinue).Name -contains "$env:COMPUTERNAME\$username"
        }
        $groups | Format-Table Name, Description -AutoSize
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

function Enable-UserAccountFn {
    Write-Host "`n========== HESABI ETKİNLEŞTİRME ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nEtkinleştirmek istediğiniz kullanıcı adı"

    try {
        Enable-LocalUser -Name $username -ErrorAction Stop
        Write-Host "`n'$username' hesabı başarıyla etkinleştirildi!" -ForegroundColor Green
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

function Disable-UserAccountFn {
    Write-Host "`n========== HESABI PASİFLEŞTİRME ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nPasifleştirmek istediğiniz kullanıcı adı"

    try {
        Disable-LocalUser -Name $username -ErrorAction Stop
        Write-Host "`n'$username' hesabı başarıyla pasifleştirildi!" -ForegroundColor Green
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

function Remove-UserAccountFn {
    Write-Host "`n========== HESABI KALICI OLARAK SİLME ==========" -ForegroundColor Cyan
    Show-Users

    $username = Read-Host "`nKalıcı olarak silmek istediğiniz kullanıcı adı"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        $userSID = $user.SID.Value

        # Kullanıcı profil yolunu bul
        $profilePath = $null
        $regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$userSID"

        if (Test-Path $regPath) {
            $profileInfo = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
            $profilePath = $profileInfo.ProfileImagePath
        }

        # Kayıt defterinde bulunamazsa varsayılan yolu kullan
        if (-not $profilePath) {
            $profilePath = "$env:SystemDrive\Users\$username"
        }

        # Silinecek bilgileri göster
        Write-Host "`n========== SİLİNECEK BİLGİLER ==========" -ForegroundColor Yellow
        Write-Host "Kullanıcı adı     : $username" -ForegroundColor White
        Write-Host "Tam ad            : $($user.FullName)" -ForegroundColor White
        Write-Host "Hesap durumu      : $($user.Enabled)" -ForegroundColor White
        Write-Host "Son oturum        : $($user.LastLogon)" -ForegroundColor White
        Write-Host "Profil dizini     : $profilePath" -ForegroundColor White
        Write-Host "SID               : $userSID" -ForegroundColor White

        # Profil dizininin varlığını kontrol et
        $profileExists = Test-Path $profilePath
        if ($profileExists) {
            try {
                $folderSize = (Get-ChildItem -Path $profilePath -Recurse -Force -ErrorAction SilentlyContinue |
                               Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
                Write-Host "Dizin boyutu      : $([math]::Round($folderSize, 2)) MB" -ForegroundColor White
            }
            catch {
                Write-Host "Dizin boyutu      : Hesaplanamadı" -ForegroundColor Gray
            }
        }
        else {
            Write-Host "Profil dizini     : Bulunamadı (zaten silinmiş olabilir)" -ForegroundColor Gray
        }

        Write-Host "`n========================================" -ForegroundColor Yellow
        Write-Host "DİKKAT: BU İŞLEM GERİ ALINAMAZ!" -ForegroundColor Red
        Write-Host "========================================" -ForegroundColor Yellow
        Write-Host "`nSilinecekler:" -ForegroundColor Red
        Write-Host "  - Kullanıcı hesabı" -ForegroundColor White
        Write-Host "  - Kullanıcı profil dizini ($profilePath)" -ForegroundColor White
        Write-Host "  - Tüm kullanıcı dosyaları (Belgeler, Masaüstü, vb.)" -ForegroundColor White
        Write-Host "  - Kayıt defteri girdileri" -ForegroundColor White

        Write-Host ""
        $confirm1 = Read-Host "Devam etmek istediğinizden EMİN MİSİNİZ? (EVET/HAYIR)"

        if ($confirm1 -eq "EVET") {
            Write-Host "`nSON UYARI: Tüm veriler kalıcı olarak silinecek!" -ForegroundColor Red
            $confirm2 = Read-Host "Onaylamak için kullanıcı adını tekrar yazın"

            if ($confirm2 -eq $username) {

                Write-Host "`n========== SİLME İŞLEMİ BAŞLIYOR ==========" -ForegroundColor Yellow

                # 1. Önce kullanıcının açık oturumunu kapat (aktif veya bağlantısı kesilmiş)
                Write-Host "`n[1/5] Aktif oturumlar kontrol ediliyor..." -ForegroundColor Cyan
                try {
                    $sessionLines = quser 2>$null | Select-String $username
                    if ($sessionLines) {
                        foreach ($line in $sessionLines) {
                            # logoff.exe kullanıcı adı değil, oturum ID'si veya SESSIONNAME
                            # ister. Doğrudan kullanıcı adı verilirse sessizce başarısız
                            # olur ve oturum (ve yüklü kayıt defteri hive'ı) açık kalır -
                            # özellikle Hızlı Kullanıcı Değiştirme kullanıldığında önceki
                            # oturum "Disc" (bağlantısı kesilmiş) olarak görünür ve
                            # SESSIONNAME sütunu boş kalarak quser çıktısındaki alan
                            # sıralamasını kaydırır.
                            $tokens = ($line.ToString().Trim() -replace '^>', '').Trim() -split '\s+'
                            $sessionId = $tokens | Where-Object { $_ -match '^\d+$' } | Select-Object -First 1

                            if ($sessionId) {
                                Write-Host "  -> Oturum bulundu (ID: $sessionId), kapatılıyor..." -ForegroundColor Yellow
                                logoff $sessionId 2>$null
                                Start-Sleep -Seconds 2
                            }
                        }
                    }
                    else {
                        Write-Host "  -> Aktif veya bağlantısı kesilmiş oturum yok" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "  -> Oturum kontrolü atlandı" -ForegroundColor Gray
                }

                # 1b. quser yalnızca interaktif/RDP oturumlarını gösterir. Bir işlem, bu
                # yollardan hiçbiri olmadan (runas, zamanlanmış görev, servis, başka bir
                # yönetici hesabından başlatılan arka plan işlemi) bu kullanıcı adına
                # çalışıyor olabilir. Böyle bir işlem, kayıt defteri hive yükleme sayacını
                # sıfırın üzerinde tutar ve ilerideki dizin silme adımını engeller; bu
                # yüzden oturum kontrolünden bağımsız olarak ayrıca aranıp durdurulmalı.
                Write-Host "`n[2/5] Bu kullanıcı adına çalışan arka plan işlemleri kontrol ediliyor..." -ForegroundColor Cyan
                try {
                    $ownedProcesses = @()
                    $allProcesses = Get-CimInstance -ClassName Win32_Process -ErrorAction Stop
                    foreach ($proc in $allProcesses) {
                        try {
                            $owner = Invoke-CimMethod -InputObject $proc -MethodName GetOwner -ErrorAction Stop
                            if ($owner.User -eq $username) {
                                $ownedProcesses += [PSCustomObject]@{
                                    ProcessId = $proc.ProcessId
                                    Name      = $proc.Name
                                }
                            }
                        }
                        catch {
                            # Bazı sistem işlemleri sahip bilgisi vermez, atla
                        }
                    }

                    if ($ownedProcesses.Count -gt 0) {
                        Write-Host "  -> '$username' adına çalışan $($ownedProcesses.Count) işlem bulundu:" -ForegroundColor Yellow
                        $ownedProcesses | Format-Table ProcessId, Name -AutoSize

                        $confirmKill = Read-Host "  Bu işlemleri şimdi sonlandırmak istiyor musunuz? (EVET/HAYIR)"
                        if ($confirmKill -eq "EVET") {
                            foreach ($p in $ownedProcesses) {
                                try {
                                    Stop-Process -Id $p.ProcessId -Force -ErrorAction Stop
                                    Write-Host "  [TAMAM] PID $($p.ProcessId) ($($p.Name)) sonlandırıldı" -ForegroundColor Green
                                }
                                catch {
                                    Write-Host "  [UYARI] PID $($p.ProcessId) ($($p.Name)) sonlandırılamadı - $($_.Exception.Message)" -ForegroundColor Yellow
                                }
                            }
                            Start-Sleep -Seconds 2
                        }
                        else {
                            Write-Host "  -> Atlandı. Bu işlemler kayıt defteri hive'ını kilitli tutabilir; bu da 5. adımdaki profil dizini silme işleminin geride bir kalıntı bırakmasına yol açabilir." -ForegroundColor Yellow
                        }
                    }
                    else {
                        Write-Host "  -> Bu kullanıcı için arka plan işlemi bulunamadı" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "  -> İşlem kontrolü atlandı: $($_.Exception.Message)" -ForegroundColor Gray
                }

                # 2. Kullanıcı hesabını sil
                Write-Host "`n[3/5] Kullanıcı hesabı siliniyor..." -ForegroundColor Cyan
                try {
                    Remove-LocalUser -Name $username -ErrorAction Stop
                    Write-Host "  [TAMAM] Kullanıcı hesabı başarıyla silindi" -ForegroundColor Green
                }
                catch {
                    Write-Host "  [HATA] Kullanıcı hesabı silinemedi - $($_.Exception.Message)" -ForegroundColor Red
                    Read-Host "`nÇıkmak için ENTER tuşuna basın."
                    return
                }

                # 3. Kayıt defteri profilini sil
                Write-Host "`n[4/5] Kayıt defteri profili siliniyor..." -ForegroundColor Cyan
                try {
                    if (Test-Path $regPath) {
                        Remove-Item -Path $regPath -Recurse -Force -ErrorAction Stop
                        Write-Host "  [TAMAM] Kayıt defteri profili silindi" -ForegroundColor Green
                    }
                    else {
                        Write-Host "  -> Kayıt defteri profili bulunamadı" -ForegroundColor Gray
                    }
                }
                catch {
                    Write-Host "  [UYARI] Kayıt defteri temizlenemedi - $($_.Exception.Message)" -ForegroundColor Yellow
                }

                # 3b. Hesabın kayıt defteri hive'ı hâlâ HKEY_USERS altında yüklüyse,
                # dosya izinlerinden bağımsız olarak NTUSER.DAT işletim sistemi
                # düzeyinde kilitli kalır. Profil dizinine dokunmadan önce hive'ı
                # açıkça boşalt.
                Write-Host "`n[4b/5] Yüklü kayıt defteri hive'ı kontrol ediliyor..." -ForegroundColor Cyan
                try {
                    $hiveMounted = Test-Path "Registry::HKEY_USERS\$userSID"
                    if ($hiveMounted) {
                        Write-Host "  -> Hive hâlâ yüklü, boşaltılıyor..." -ForegroundColor Yellow
                        [gc]::Collect()
                        [gc]::WaitForPendingFinalizers()
                        reg unload "HKU\$userSID" 2>$null | Out-Null
                        Start-Sleep -Seconds 1
                    }
                    else {
                        Write-Host "  -> Yüklü hive bulunamadı" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "  -> Hive boşaltma atlandı: $($_.Exception.Message)" -ForegroundColor Gray
                }

                # 4. Kullanıcı profil dizinini sil
                Write-Host "`n[5/5] Kullanıcı profil dizini siliniyor..." -ForegroundColor Cyan
                if ($profileExists) {
                    try {
                        # Dosyaların kullanımda olmaması için kısa bekleme
                        Start-Sleep -Seconds 2

                        Remove-Item -Path $profilePath -Recurse -Force -ErrorAction Stop
                    }
                    catch {
                        Write-Host "  [UYARI] Bazı dosyalar silinemedi" -ForegroundColor Yellow
                        Write-Host "  Hata: $($_.Exception.Message)" -ForegroundColor Red

                        # Alternatif silme yöntemi dene
                        Write-Host "`n  -> Alternatif yöntem deneniyor..." -ForegroundColor Yellow
                        try {
                            # takeown ve icacls ile sahiplik ve izin devri
                            takeown /f $profilePath /r /d y 2>$null | Out-Null
                            icacls $profilePath /grant administrators:F /t 2>$null | Out-Null
                            Remove-Item -Path $profilePath -Recurse -Force -ErrorAction Stop
                        }
                        catch {
                            Write-Host "  [HATA] Silme denemesi hata verdi - $($_.Exception.Message)" -ForegroundColor Red
                        }
                    }

                    # Sessiz bir "başarılı" mesajına güvenme - klasörün gerçekten
                    # yok olduğunu doğrula. Yüklü bir hive, Remove-Item hata
                    # vermeden dönse bile işletim sisteminin klasörü henüz tam
                    # serbest bırakmamış olmasına yol açabilir; bu yüzden kısa
                    # aralıklarla birkaç kez yeniden kontrol et.
                    $stillThere = $true
                    for ($i = 0; $i -lt 3; $i++) {
                        if (-not (Test-Path $profilePath)) {
                            $stillThere = $false
                            break
                        }
                        Start-Sleep -Seconds 1
                    }

                    if (-not $stillThere) {
                        Write-Host "  [TAMAM] Kullanıcı profil dizini silindi ve doğrulandı: $profilePath" -ForegroundColor Green
                    }
                    else {
                        Write-Host "  [UYARI] Silme denemesi ve doğrulama beklemesinden sonra dizin hâlâ mevcut." -ForegroundColor Yellow
                        Write-Host "  Bu genellikle bir kayıt defteri hive'ının veya başka bir işlemin dizini hâlâ kilitli tuttuğu anlamına gelir." -ForegroundColor Yellow
                        Write-Host "  [HATA] Dizin tamamen silinemedi. Elle silme gerekebilir:" -ForegroundColor Red
                        Write-Host "    $profilePath" -ForegroundColor White
                        Write-Host "`n  Elle silmek için:" -ForegroundColor Yellow
                        Write-Host "    1. Dosya Gezgini'ni yönetici olarak açın" -ForegroundColor White
                        Write-Host "    2. $profilePath dizinine gidin" -ForegroundColor White
                        Write-Host "    3. Sağ tıklayıp 'Sil' seçeneğini kullanın" -ForegroundColor White
                    }
                }
                else {
                    Write-Host "  -> Kullanıcı dizini bulunamadı (zaten silinmiş)" -ForegroundColor Gray
                }

                # Özet
                Write-Host "`n========================================" -ForegroundColor Green
                Write-Host "          SİLME İŞLEMİ TAMAMLANDI" -ForegroundColor Green
                Write-Host "========================================" -ForegroundColor Green
                Write-Host "`n'$username' kullanıcısı sistemden tamamen kaldırıldı!" -ForegroundColor Green

            }
            else {
                Write-Host "`nKullanıcı adı eşleşmedi. İşlem iptal edildi." -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "`nİşlem iptal edildi." -ForegroundColor Yellow
        }
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

function Create-NewUser {
    Write-Host "`n========== YENİ HESAP OLUŞTURMA ==========" -ForegroundColor Cyan

    $username = Read-Host "Yeni kullanıcı adı"
    $fullname = Read-Host "Tam ad (opsiyonel)"
    $description = Read-Host "Açıklama (opsiyonel)"
    $password = Read-Host "Şifre" -AsSecureString

    try {
        $params = @{
            Name                 = $username
            Password             = $password
            PasswordNeverExpires = $true
        }

        if ($fullname) { $params.Add("FullName", $fullname) }
        if ($description) { $params.Add("Description", $description) }

        New-LocalUser @params -ErrorAction Stop
        Write-Host "`n'$username' hesabı başarıyla oluşturuldu!" -ForegroundColor Green

        # Gruba ekle
        $addToGroup = Read-Host "`nBu hesabı bir gruba eklemek ister misiniz? (E/H)"
        if ($addToGroup -eq "E" -or $addToGroup -eq "e") {
            Write-Host "`nMevcut Gruplar:" -ForegroundColor Yellow
            Get-LocalGroup | Format-Table Name, Description -AutoSize

            $groupName = Read-Host "`nGrup adı (örnek: Administrators, Users)"
            try {
                Add-LocalGroupMember -Group $groupName -Member $username -ErrorAction Stop
                Write-Host "`n'$username', '$groupName' grubuna eklendi!" -ForegroundColor Green
            }
            catch {
                Write-Host "`nHATA: Gruba eklenirken hata oluştu - $($_.Exception.Message)" -ForegroundColor Red
            }
        }
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

function Reset-UserFromRegistry {
    Write-Host "`n========== KAYIT DEFTERİNDEN HESAP SIFIRLAMA ==========" -ForegroundColor Cyan
    Write-Host "DİKKAT: Bu ileri düzey bir işlemdir, dikkatli kullanın!" -ForegroundColor Red
    Write-Host ""

    Show-Users

    $username = Read-Host "`nSıfırlamak istediğiniz kullanıcı adı"

    try {
        $user = Get-LocalUser -Name $username -ErrorAction Stop
        $sid = $user.SID.Value

        Write-Host "`nKullanıcı SID: $sid" -ForegroundColor Yellow

        $confirm = Read-Host "`nBu kullanıcının kayıt defteri profil girdisini sıfırlamak istiyor musunuz? (E/H)"

        if ($confirm -eq "E" -or $confirm -eq "e") {
            $profilePath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid"

            if (Test-Path $profilePath) {
                Write-Host "`nProfil kayıt defteri anahtarı bulundu: $profilePath" -ForegroundColor Green

                $profileInfo = Get-ItemProperty -Path $profilePath
                Write-Host "`nMevcut profil dizini: $($profileInfo.ProfileImagePath)" -ForegroundColor Yellow

                $deleteProfile = Read-Host "`nBu kayıt defteri profil girdisini silmek istiyor musunuz? (E/H)"
                if ($deleteProfile -eq "E" -or $deleteProfile -eq "e") {
                    Remove-Item -Path $profilePath -Recurse -Force
                    Write-Host "`nKayıt defteri profil girdisi silindi!" -ForegroundColor Green
                    Write-Host "NOT: Kullanıcı bir sonraki oturum açışında yeni bir profil alacaktır." -ForegroundColor Yellow
                }
            }
            else {
                Write-Host "`nBu kullanıcı için kayıt defterinde profil girdisi bulunamadı." -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "`nİşlem iptal edildi." -ForegroundColor Yellow
        }
    }
    catch {
        Write-Host "`nHATA: $($_.Exception.Message)" -ForegroundColor Red
    }

    Read-Host "`nÇıkmak için ENTER tuşuna basın."
}

# Ana program döngüsü
do {
    Show-Banner
    Show-Menu

    $choice = Read-Host "Seçiminiz"

    switch ($choice) {
        "1" {
            Show-Banner
            Show-Users
            Read-Host "`nÇıkmak için ENTER tuşuna basın."
        }
        "2" {
            Manage-Users
        }
        "3" {
            Show-Banner
            Create-NewUser
        }
        "4" {
            Show-Banner
            Reset-UserFromRegistry
        }
        "0" {
            Write-Host "`nProgram kapatılıyor..." -ForegroundColor Yellow
            Start-Sleep -Seconds 1
            exit
        }
        default {
            Write-Host "`nGeçersiz seçim! Lütfen 0-4 arası bir sayı girin." -ForegroundColor Red
            Start-Sleep -Seconds 2
        }
    }

} while ($true)
Güle güle kullanın...
Cevapla

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