<# ============================================================ Yuhalu Pre-Install Check Script IMPORTANT NOTICE: ----------------- This script is part of the official Yuhalu installer. • Do NOT modify this file unless you fully understand PowerShell and the Yuhalu installation process. • Modifying this script may result in: - Broken installation - Corrupted files - Security risks • Yuhalu is not responsible for issues caused by modified installer scripts. • It is used to install Yuhalu for the first time for Window 10 and 11. For official updates, download from: https://www.yuhalu.com/download/ ============================================================ #> <# ========================= Original file: YuhaluInstaller.ps1 1/9/2026 powershell -ExecutionPolicy Bypass -File "C:\YuhaluDistribution\YuhaluV2.0.Script.ps1" ========================= ========================================================== Procedure: Install Yuhalu for the First Time ========================================================= 1. Double-click the batch file to run the installer script. 2. Click **Select Zip Folder**, then browse to and select the folder where the downloaded Yuhalu ZIP files are located. Please note that this button is active only when the two checkboxes are checked. 3. If all prerequisite checkboxes are checked, it means: - Java (with a supported version) is installed - 7-Zip is installed and available 4. Click the **Install** button when it becomes active. Note: The **Launch Yuhalu** button will remain disabled at this stage. 5. After installation completes successfully, the **Launch Yuhalu** button will turn green and become active. 6. You may scroll up or down in the **Status Log** area to review installation messages. 7. When ready, click **Launch Yuhalu** to start the application. The Yuhalu main window will appear. 8. Click **Exit** at any time to close the installer. ========================================================= #> Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $script:ShowConsoleLog = $false # set to $true for debugging, trigger at Write-Log $width = 560 # component width # =============================== # Constants # =============================== $YuhaluRoot = "C:\Yuhalu" $LogRoot = "C:\Yuhalu\Temp\InstallLogs" $TimeStamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $LogFile = Join-Path $LogRoot "Yuhalu-Install-$TimeStamp.log" $javaUrl = "https://www.java.com/download/" $zipUrl = "https://www.7-zip.org/" $javaEclipse = "https://adoptium.net/" # =============================== # Logging # =============================== if (!(Test-Path $LogRoot)) { New-Item -ItemType Directory -Path $LogRoot -Force | Out-Null } function Write-Log { param( [string]$Level, [string]$Message ) $time = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $line = "[$time] [$Level] $Message" if ($script:ShowConsoleLog) { Write-Host $line } Add-Content -Path $LogFile -Value $line } Write-Log "INFO" "Yuhalu Pre-Install Check started" Write-Log "INFO" "OS: $([System.Environment]::OSVersion.VersionString)" Write-Log "INFO" "User: $env:USERNAME" # =============================== # Helpers # =============================== function Find-Executable($name, $extraPaths = @()) { $cmd = Get-Command $name -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } foreach ($p in $extraPaths) { if (Test-Path $p) { return $p } } return $null } function New-UrlBox { param([string]$Url) $tb = New-Object System.Windows.Forms.TextBox $tb.Text = $Url $tb.ReadOnly = $true $tb.BorderStyle = "FixedSingle" $tb.Dock = "Top" $tb.Width = 560 $tb.TabStop = $false $tb.Add_Click({ $this.SelectAll() }) $tb.Add_Enter({ $this.SelectAll() }) return $tb } #Horizontal Line (Separator) Function #Usage: $root.Controls.Add((New-HorizontalLine)) function New-HorizontalLine { param ( [int]$Width = $width, [int]$Height = 1, [System.Drawing.Color]$Color = [System.Drawing.Color]::LightBlue, [int]$MarginTop = 10, [int]$MarginBottom = 10 ) $line = New-Object System.Windows.Forms.Panel $line.Height = $Height $line.Width = $Width $line.BackColor = $Color $line.Margin = New-Object System.Windows.Forms.Padding(0, $MarginTop, 0, $MarginBottom) return $line } # =============================== # Detect prerequisites # =============================== function Get-JavaInfo { # Possible Java locations (Temurin, Oracle, others) $javaCandidates = @( "$env:JAVA_HOME\bin\java.exe", "C:\Program Files\Java\*\bin\java.exe", "C:\Program Files\Eclipse Adoptium\*\bin\java.exe", "C:\Program Files (x86)\Java\*\bin\java.exe" ) # Try PATH first $javaExe = Get-Command java.exe -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source # If not found in PATH, search known locations if (-not $javaExe) { foreach ($pattern in $javaCandidates) { $found = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue | Select-Object -First 1 if ($found) { $javaExe = $found.FullName break } } } if (-not $javaExe) { return [PSCustomObject]@{ Found = $false Major = $null Path = $null Raw = $null } } # Get version (java writes to STDERR) $out = & "$javaExe" -version 2>&1 $match = [regex]::Match($out, 'version\s+"([^"]+)"') if (-not $match.Success) { return [PSCustomObject]@{ Found = $true Major = $null Path = $javaExe Raw = $out } } $ver = $match.Groups[1].Value if ($ver -match '^1\.(\d+)') { $major = [int]$matches[1] # Java 8 } else { $major = [int]($ver -split '\.')[0] # Java 9+ } return [PSCustomObject]@{ Found = $true Major = $major Path = $javaExe Raw = $out } } $javaInfo = Get-JavaInfo $checkboxes = @() $checkboxes += [PSCustomObject]@{ Name = "Java Runtime (11+ required)" Found = ($javaInfo.Found -and $javaInfo.Major -ge 11) Path = if ($javaInfo.Found) { "Java $($javaInfo.Major)" } else { $null } } # $sevenZip = Find-Executable "7z.exe" @( "C:\Program Files\7-Zip\7z.exe", "C:\Program Files (x86)\7-Zip\7z.exe" ) $checkboxes += [PSCustomObject]@{ Name = "7-Zip (7z.exe)" Found = [bool]$sevenZip Path = $sevenZip } $javaFound = [bool]($checkboxes | Where-Object { $_.Name -like "*Java*" } | Select-Object -First 1 -ExpandProperty Found -ErrorAction SilentlyContinue) $zipFound = [bool]($checkboxes | Where-Object { $_.Name -like "*7-Zip*" } | Select-Object -First 1 -ExpandProperty Found -ErrorAction SilentlyContinue) $javaSupported = ($javaInfo.Found -and $javaInfo.Major -ge 11) Write-Log "INFO" "Prereq Java Found=$($javaInfo.Found) Version=$($javaInfo.Major) Supported=$javaSupported" Write-Log "INFO" "Prereq 7-Zip Found=$zipFound Path=$sevenZip" $IsFirstInstall = !(Test-Path $YuhaluRoot) Write-Log "INFO" ("Install mode: " + ($(if ($IsFirstInstall) { "FIRST INSTALL" } else { "UPDATE" }))) #zipPanel for zip files $zipPanel = New-Object System.Windows.Forms.ListView $zipPanel.View = "Details" $zipPanel.FullRowSelect = $true $zipPanel.Size = New-Object System.Drawing.Size($width, 240) $zipPanel.GridLines = $true #$zipPanel.Dock = "Fill" $zipPanel.Font = New-Object System.Drawing.Font("Segoe UI", 11) $zipPanel.Columns.Add("Name", 270, [System.Windows.Forms.HorizontalAlignment]::Left) | Out-Null $zipPanel.Columns.Add("Size (MB)", 90, [System.Windows.Forms.HorizontalAlignment]::Right) | Out-Null $zipPanel.Columns.Add("Last Modified", 195, [System.Windows.Forms.HorizontalAlignment]::Left) | Out-Null $zipPanel.Font = $baseFont $zipPanel.Add_ControlAdded({ # Automatically set font when controls are added param($sender, $e) $e.Control.Font = $sender.Font }) # 1/11/26 $btnSelectFolder = New-Object System.Windows.Forms.Button $btnSelectFolder.Text = "Select ZIP Folder" $btnSelectFolder.AutoSize = $true $btnSelectFolder.Location = New-Object System.Drawing.Point(0, 6) $btnSelectFolder.Enabled = $false $lblZipHint = New-Object System.Windows.Forms.Label $lblZipHint.AutoSize = $true $lblZipHint.Location = New-Object System.Drawing.Point(155, 10) $lblZipHint.ForeColor = [System.Drawing.Color]::DarkRed $lblZipHint.Text = "Java and 7-Zip must be installed first." $panelZipSelect = New-Object System.Windows.Forms.Panel $panelZipSelect.AutoSize = $true $panelZipSelect.Controls.Add($btnSelectFolder) $panelZipSelect.Controls.Add($lblZipHint) # show contional message base on java and 7-zip $lblStatus = New-Object System.Windows.Forms.Label $lblStatus.AutoSize = $false $lblStatus.Width = $width $lblStatus.Height = 130 $lblStatus.ForeColor = [System.Drawing.Color]::Black $lblStatus.TextAlign = "TopLeft" <# #1/12/26 $urlPanel = New-Object System.Windows.Forms.FlowLayoutPanel $urlPanel.FlowDirection = 'TopDown' $urlPanel.WrapContents = $false $urlPanel.AutoSize = $true $urlPanel.AutoSizeMode = 'GrowAndShrink' $urlPanel.Dock = 'Top' $urlPanel.Visible = $true #> # status label + url panel $statusContainer = New-Object System.Windows.Forms.FlowLayoutPanel $statusContainer.FlowDirection = "TopDown" $statusContainer.AutoSize = $true #$statusContainer.Dock = "Fill" $statusContainer.Controls.Add($lblStatus) | Out-Null #$statusContainer.Controls.Add($urlPanel) | Out-Null #checkboxes panel $checkboxPanel = New-Object System.Windows.Forms.FlowLayoutPanel $checkboxPanel.FlowDirection = "TopDown" $checkboxPanel.WrapContents = $false $checkboxPanel.AutoSize = $true $checkboxPanel.Dock = "Fill" $lblInstall = New-Object System.Windows.Forms.Label $lblInstall.Text = "Installation directory:" $lblInstall.AutoSize = $true $txtInstall = New-Object System.Windows.Forms.TextBox $txtInstall.Text = $YuhaluRoot $txtInstall.ReadOnly = $true $txtInstall.BorderStyle = "FixedSingle" $txtInstall.Width = $width $txtInstall.TabStop = $false $lblLog = New-Object System.Windows.Forms.Label $lblLog.AutoSize = $true $lblLog.ForeColor = [System.Drawing.Color]::Blue $lblLog.Text = "Status Log" # install directory $installPanel = New-Object System.Windows.Forms.FlowLayoutPanel $installPanel.Size = New-Object System.Drawing.Size($width, 140) #1/11/26 $installPanel.FlowDirection = "TopDown" $installPanel.WrapContents = $false $installPanel.AutoSize = $true $installPanel.Dock = "Fill" $installPanel.Controls.Add($lblInstall) | Out-Null $installPanel.Controls.Add($txtInstall) | Out-Null $installPanel.Controls.Add($lblLog) | Out-Null $btnInstall = New-Object System.Windows.Forms.Button $btnInstall.Text = "Install" $btnInstall.Size = New-Object System.Drawing.Size(90, 35) $btnInstall.Enabled = $false #1/12/26 $btnLaunch = New-Object System.Windows.Forms.Button $btnLaunch.Text = "Launch Yuhalu" $btnLaunch.AutoSize = $true; $btnLaunch.Enabled = $false $btnExit = New-Object System.Windows.Forms.Button $btnExit.Text = "Exit" $btnExit.Size = New-Object System.Drawing.Size(90, 35) #$btnExit.BackColor = [System.Drawing.Color]::Red $btnExit.ForeColor = [System.Drawing.Color]::Red # For action buttons $actionPanel = New-Object System.Windows.Forms.FlowLayoutPanel $actionPanel.WrapContents = $false $actionPanel.Dock = "Fill" $actionPanel.AutoSize = $true; $actionPanel.Controls.Add($btnInstall) | Out-Null $actionPanel.Controls.Add($btnLaunch) | Out-Null $actionPanel.Controls.Add($btnExit) | Out-Null # =============================== # Build UI # =============================== $root = New-Object System.Windows.Forms.TableLayoutPanel #Object panel $root.Dock = "Fill" $root.ColumnCount = 1 $root.RowCount = 8 $root.Padding = New-Object System.Windows.Forms.Padding(10) $root.AutoSizeMode = 'GrowAndShrink' $root.RowStyles.Clear() | Out-Null $root.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null # zip panel 1..8 | ForEach-Object { $root.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize))) | Out-Null } $baseFont = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Regular) $form = New-Object System.Windows.Forms.Form # dialog $form.Text = "Yuhalu Installer" $form.Size = New-Object System.Drawing.Size(600, 720) $form.StartPosition = "CenterScreen" $form.TopMost = $true $form.Font = $baseFont $form.Controls.Add($root) | Out-Null #This will close dialog without Installation $script:InstallCancelled = $false $form.Add_FormClosing({ if ($_.CloseReason -eq [System.Windows.Forms.CloseReason]::UserClosing) { $script:InstallCancelled = $true } }) #----------------- # dialog display layout sequence on the screen $root.Controls.Add($zipPanel, 0, 0) | Out-Null $root.Controls.Add($panelZipSelect, 0, 1) | Out-Null $root.Controls.Add((New-HorizontalLine), 0, 2) | Out-Null $root.Controls.Add($checkboxPanel, 0, 3) | Out-Null $root.Controls.Add($statusContainer, 0, 4) | Out-Null $root.Controls.Add($installPanel, 0, 5) | Out-Null $root.Controls.Add((New-HorizontalLine), 0, 6) | Out-Null $root.Controls.Add($actionPanel, 0, 7) | Out-Null #----------------- # 1/11/26 Test <# $panelZipSelect.Visible = $false $checkboxPanel.Visible = $false $statusContainer.Visible = $false $statusContainer.Visible = $false $actionPanel.Visible = $false #---------- #> function Update-ZipSelectState { if ($javaFound -and $zipFound) { $btnSelectFolder.Enabled = $true $lblZipHint.Text = "Be sure to select the correct ZIP folder before installing." $lblZipHint.ForeColor = [System.Drawing.Color]::DarkGreen } else { $btnSelectFolder.Enabled = $false $lblZipHint.Text = "Java and/or 7-Zip must be installed first." $lblZipHint.ForeColor = [System.Drawing.Color]::DarkRed } } Update-ZipSelectState # invoke Function above function Set-InstallStatus($text) { $lblStatus.Text = $text } # =============================== # Bind data to UI # =============================== $checkboxPanel.Controls.Clear() | Out-Null foreach ($c in $checkboxes) { $cb = New-Object System.Windows.Forms.CheckBox $cb.AutoSize = $true $cb.TabStop = $false $cb.Enabled = $true if ($c.Found) { #$cb.Text = "[OK] $($c.Name)" $cb.Text = "$($c.Name)" $cb.Checked = $true } else { #$cb.Text = "[X] $($c.Name) (NOT FOUND)" $cb.Text = "$($c.Name) (NOT FOUND)" $cb.Checked = $false } $lockedState = $cb.Checked $cb.Add_Click({ $this.Checked = $lockedState }) $cb.Add_KeyDown({ param($s, $e) if ($e.KeyCode -eq 'Space') { $e.Handled = $true } }) $checkboxPanel.Controls.Add($cb) | Out-Null } #1/12/26 $txtInstallLog = New-Object System.Windows.Forms.RichTextBox #from belew function Write-InstallLog { param ( [string]$Message, [System.Drawing.Color]$Color = [System.Drawing.Color]::Black ) if ($txtInstallLog.InvokeRequired) { $txtInstallLog.Invoke([Action]{ $txtInstallLog.SelectionColor = $Color $txtInstallLog.AppendText("$Message`r`n") $txtInstallLog.ScrollToCaret() }) } else { $txtInstallLog.SelectionColor = $Color $txtInstallLog.AppendText("$Message`r`n") $txtInstallLog.ScrollToCaret() } Write-Host $Message # Optional: still write to console for debugging } #$urlPanel.Controls.Clear() | Out-Null $missing = $checkboxes | Where-Object { -not $_.Found } $dowloadLink = "Download links:" if ($javaFound -and $zipFound) { $lblStatus.Text = "All required components were found and checked above.`r`n" + "1. Click Select Zip Folder button to select folder where download files locate.`r`n" + "2. Click the Install button below to install when active.`r`n`r`n" + "Note: The default installation directory is shown below. " + "If you need to run Yuhalu from a different drive, you can copy the entire directory." } elseif (-not $javaFound -and -not $zipFound) { $lblStatus.Text = "Java Runtime and 7-Zip were not found on this computer.`r`n" + "Please install them, then run this installer again.`r`n`r`n" + "For download, copy the links shown in the log section below." Write-InstallLog $dowloadLink Write-InstallLog $javaEclipse Write-InstallLog $javaUrl Write-InstallLog $zipUrl } elseif (-not $javaFound) { $lblStatus.Text = "Java 11 or newer is required.`r`n" + "Java 8 is not supported.`r`n" + "Java 17 i recommended.`r`n" Write-InstallLog $dowloadLink Write-InstallLog $javaEclipse Write-InstallLog $javaUrl } else { $lblStatus.Text = "7-Zip was not found on this computer.`r`n`r`n" + "7-Zip is required to extract installation files.`r`n" + "Please install it, then run this installer again.`r`n`r`n" + "For download, copy the link shown in the log section below." Write-InstallLog $dowloadLink Write-InstallLog $zipUrl } # make sure everything is installed and selected to enable $btnInstall function Update-InstallState { $zipReady = ($script:SelectedZipFiles -and $script:SelectedZipFiles.Count -gt 0) if ($javaFound -and $zipFound -and $zipReady) { $btnInstall.Enabled = $true } else { $btnInstall.Enabled = $false } } # show zip files on table function Load-ZipPreview { param ([string]$FolderPath) $zipPanel.Items.Clear() | Out-Null if (!(Test-Path $FolderPath)) { Write-Log "WARN" "Selected folder does not exist: $FolderPath" return } $zipFiles = Get-ChildItem -Path $FolderPath -Filter "*.zip" -File if (!$zipFiles -or $zipFiles.Count -eq 0) { Write-Log "WARN" "No ZIP files found in folder: $FolderPath" return } foreach ($zip in $zipFiles) { $sizeMB = [Math]::Round($zip.Length / 1MB, 2) # IMPORTANT: $zip.Name is only valid here $item = New-Object System.Windows.Forms.ListViewItem(@($zip.Name)) $item.SubItems.Add($sizeMB.ToString("0.00")) | Out-Null $item.SubItems.Add($zip.LastWriteTime.ToString()) | Out-Null $zipPanel.Items.Add($item) | Out-Null } $script:SelectedZipFolder = $FolderPath $script:SelectedZipFiles = $zipFiles } # =============================== # Events # =============================== # 1/11/26 $btnSelectFolder.Add_Click({ $folderDialog = New-Object System.Windows.Forms.FolderBrowserDialog $folderDialog.Description = "Select the folder containing Yuhalu ZIP files" $folderDialog.ShowNewFolderButton = $false $folderDialog.RootFolder = [Environment+SpecialFolder]::Desktop $folderDialog.SelectedPath = "$env:USERPROFILE\Downloads" if ($folderDialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { Write-Log "INFO" "User cancelled ZIP folder selection" return } $selected = $folderDialog.SelectedPath $selectedFull = [IO.Path]::GetFullPath($selected).TrimEnd('\') # 1. Block drive root if ($selectedFull -match '^[A-Z]:$') { [System.Windows.Forms.MessageBox]::Show( "Please select a folder, not a drive.", "Folder Required", 'OK','Information' ) return } # 2. Block system folders $blocked = @( [Environment]::GetFolderPath("Windows"), [Environment]::GetFolderPath("ProgramFiles"), [Environment]::GetFolderPath("ProgramFilesX86") ) foreach ($b in $blocked) { if ($selectedFull -eq ([IO.Path]::GetFullPath($b).TrimEnd('\'))) { [System.Windows.Forms.MessageBox]::Show( "System folders cannot be selected.", "Unsafe Folder", 'OK','Error' ) return } } # 3. Require ZIPs if ((Get-ChildItem $selected -Filter "*.zip" -File -ErrorAction SilentlyContinue).Count -eq 0) { [System.Windows.Forms.MessageBox]::Show( "No ZIP files were found in this folder.", "No ZIP Files", 'OK','Warning' ) return } # ------------------------------------------------ # ZIP VALIDATION (ONLY AFTER SAFE PATH) # ------------------------------------------------ $zipCount = (Get-ChildItem -Path $selected -Filter "*.zip" -File -ErrorAction SilentlyContinue).Count if ($zipCount -eq 0) { [System.Windows.Forms.MessageBox]::Show( "No ZIP files were found in the selected folder.", "No ZIP Files", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) return } # ------------------------------------------------ # SAFE TO USE PATH # ------------------------------------------------ $script:SelectedZipFolder = $selected Load-ZipPreview $selected Update-InstallState }) # 1/11/26 Log installation messages #$txtInstallLog = New-Object System.Windows.Forms.RichTextBox $txtInstallLog.ReadOnly = $true $txtInstallLog.Multiline = $true $txtInstallLog.ScrollBars = 'Vertical' $txtInstallLog.Dock = 'Fill' $txtInstallLog.BackColor = [System.Drawing.Color]::White $txtInstallLog.Font = New-Object System.Drawing.Font("Consolas", 12) #Put it inside your existing $installPanel: $installPanel.Controls.Add($txtInstallLog) | Out-Null function Handle-InstallError { param([Exception]$Exception) # Status text (top of install area) Set-InstallStatus "Installation failed" # Log to console (safe) Write-InstallLog $Exception.Message -ForegroundColor DarkRed if ($Exception.InnerException) { Write-InstallLog $Exception.InnerException.Message -ForegroundColor Gray } # Re-enable Exit button $btnExit.Enabled = $true # Show blocking message box (CORRECT ENUMS) [System.Windows.Forms.MessageBox]::Show( $Exception.Message, "Yuhalu Installation Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) | Out-Null } # 1/11/26 # Helper functions #------------------- function Show-Info($msg) { [System.Windows.Forms.MessageBox]::Show( $msg, "Yuhalu Installer", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) | Out-Null } function Show-Question($msg) { return [System.Windows.Forms.MessageBox]::Show( $msg, "Yuhalu Installer", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question ) } # 1/12/26 function Invoke-YuhaluInstall {#Install Yuhalu block $installPath = $YuhaluRoot $zipFolder = $script:SelectedZipFolder # ------------------------------------------------ # Existing installation handling # ------------------------------------------------ function Test-YuhaluInstall($path) { if (-not (Test-Path $path)) { return $false } @("Yuhalu.jar","YuhaluRun.bat","Voice","libOther") | ForEach-Object { if (Test-Path (Join-Path $path $_)) { return $true } } return $false } function Get-YuhaluVersionInfo($path) { $file = Join-Path $path "Yuhalu.version" if (-not (Test-Path $file)) { return $null } (Get-Content $file | Where-Object { $_ -like "Version:*" }) ` -replace "^Version:\s*", "" } function Compare-Version($a, $b) { try { return ([version]$a).CompareTo([version]$b) } catch { return 0 } } # Check for existing installation if (Test-YuhaluInstall $installPath) { $existing = Get-YuhaluVersionInfo $installPath $cmp = Compare-Version $existing $currentVersion if ($cmp -lt 0) { $msg = "Older Yuhalu detected.`r`nUpgrade will preserve the old folder." } elseif ($cmp -eq 0) { $msg = "Same Yuhalu version detected.`r`nReinstall?" } else { $msg = "Newer Yuhalu detected.`r`nInstall older version anyway?" } if ((Show-Question $msg) -ne 'Yes') { return } $backup = "${installPath}_OLD_$(Get-Date -Format yyyyMMdd_HHmmss)" try { Rename-Item $installPath $backup -ErrorAction Stop } catch { Show-Info "Close Yuhalu and retry (or run as Administrator)." exit 1 } } $btnInstall.Enabled = $false # disable button # ------------------------------------------------ # Create install folder # ------------------------------------------------ New-Item -ItemType Directory -Force -Path $installPath | Out-Null Write-InstallLog "`nInstalling Yuhalu..." -ForegroundColor Green # ------------------------------------------------ # Extract ALL zip files (universal rule) # ------------------------------------------------ $zipFiles = @(Get-ChildItem $zipFolder -Filter "*.zip" -File) if ($zipFiles.Count -eq 0) { throw "No ZIP files found in $zipFolder" } foreach ($zip in $zipFiles) { # extract zip files using 7-zip Write-InstallLog "Extracting $($zip.Name)..." & $sevenZip x "`"$($zip.FullName)`"" "-o$installPath" -y if ($LASTEXITCODE -ne 0) { throw "Failed to extract $($zip.Name)" } } # ------------------------------------------------ # Flatten nested Yuhalu folder if present # Zip files has Yuhalu\Yuhalu\Audio\*.* to keep folders properly, so remove one Yuhalu level # ------------------------------------------------ $inner = Join-Path $installPath "Yuhalu" # inner level if (Test-Path $inner) { Write-InstallLog "Fixing nested folder layout..." Get-ChildItem $inner -Force | ForEach-Object { $dest = Join-Path $installPath $_.Name if (Test-Path $dest) { throw "Install conflict: $dest already exists." } Move-Item $_.FullName $installPath -Force } Remove-Item $inner -Recurse -Force } # ------------------------------------------------ # Create desktop shortcut # ------------------------------------------------ $desktop = [Environment]::GetFolderPath("Desktop") $runBat = Join-Path $installPath "YuhaluRun.bat" $ws = New-Object -ComObject WScript.Shell $sc = $ws.CreateShortcut((Join-Path $desktop "Yuhalu.lnk")) $sc.TargetPath = $runBat $sc.WorkingDirectory = $installPath #$icon = Join-Path $installPath "Icon\Yuhalu.ico" $icon = Join-Path $installPath "icon/monghat.ico" # icon size 128x128 if (Test-Path $icon) { $sc.IconLocation = $icon } $sc.Save() Write-InstallLog "Yuhalu installation completed successfully!" ([System.Drawing.Color]::DarkGreen) Set-InstallStatus "Installation completed successfully" # 1/12/26 Turn launch button to green color $btnLaunch.BackColor = [System.Drawing.Color]::LightGreen $btnLaunch.Enabled = $true $btnLaunch.Focus() } $btnInstall.Add_Click({ #click Install button if active to install try { Invoke-YuhaluInstall #Install Yuhalu app } catch { Handle-InstallError $_.Exception } }) $btnLaunch.Add_Click({ # Click Launch Yuhalu button to run $runBat = Join-Path $YuhaluRoot "YuhaluRun.bat" Start-Process $runBat $form.Close() }) $btnExit.Add_Click({ #Exit installation $script:InstallCancelled = $true $form.Close() }) # =============================== # Show UI or the installation dialog # =============================== [void]$form.ShowDialog() if ($script:InstallCancelled) { Write-Log "WARN" "Installation cancelled by user" exit 1 }