param( [ValidateSet('x86', 'x64')] [string]$Architecture, [switch]$AutoInstall, [switch]$ReinstallX86, [switch]$SkipDotNet, [switch]$SilentAppInstall, [switch]$CheckOnly ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $script:LogBox = $null $Config = @{ Brand = @{ LogoUrl = 'https://lh3.googleusercontent.com/d/1bR3hIMfVTKrNEGM3sndbfqZIGoUbTP4I' LogoFileName = 'matbao-logo.png' } App = @{ ProductCode = '{6FA83B51-8893-4F11-923A-0F591CF7463E}' ProductCodes = @( '{6FA83B51-8893-4F11-923A-0F591CF7463E}', '{71AEDF52-0173-4759-98FA-824F2AFD8762}' ) x86 = @{ Name = 'Mắt Bão Invoice USB Token 32 bit' Url = 'https://demo.matbao.in/rl/Setup-MatBao-Invoice-v3-x86.zip?v=20240410' FileName = 'Setup-MatBao-Invoice-v3-x86.zip' } x64 = @{ Name = 'Mắt Bão Invoice USB Token 64 bit' Url = 'https://demo.matbao.in/rl/Setup-MatBao-Invoice-v3.zip?v=20240223' FileName = 'Setup-MatBao-Invoice-v3-x64.zip' } Legacy = @{ Name = 'Mắt Bão Invoice 2.0 cho Windows 7/8' Url = 'https://matbao.in/wp-content/uploads/2024/01/Setup-MatBao-Invoice-v2.zip' FileName = 'Setup-MatBao-Invoice-v2.zip' ProductCode = '{71AEDF52-0173-4759-98FA-824F2AFD8762}' InstallDirectory = 'C:\Matbao-invoice' ExeName = 'MATBAO-INVOICE-APP.exe' ConfigName = 'MATBAO-INVOICE-APP.exe.config' } } DotNet = @{ x86 = @{ Name = '.NET 6 Desktop Runtime x86' Url = 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/6.0.36/windowsdesktop-runtime-6.0.36-win-x86.exe' FileName = 'windowsdesktop-runtime-6.0.36-win-x86.exe' } x64 = @{ Name = '.NET 6 Desktop Runtime x64' Url = 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/6.0.36/windowsdesktop-runtime-6.0.36-win-x64.exe' FileName = 'windowsdesktop-runtime-6.0.36-win-x64.exe' } Framework46 = @{ Name = '.NET Framework 4.6' Url = 'https://download.microsoft.com/download/c/3/a/c3a5200b-d33c-47e9-9d70-2f7c65daad94/NDP46-KB3045557-x86-x64-AllOS-ENU.exe' FileName = 'NDP46-KB3045557-x86-x64-AllOS-ENU.exe' MinimumRelease = 393295 } } VisualCpp = @{ x86 = @{ Name = 'Microsoft Visual C++ 2015 Redistributable x86' Url = 'https://download.microsoft.com/download/9/3/f/93fcf1e7-e6a4-478b-96e7-d4b285925b00/vc_redist.x86.exe' FileName = 'vc_redist.x86.exe' } x64 = @{ Name = 'Microsoft Visual C++ 2015 Redistributable x64' Url = 'https://download.microsoft.com/download/9/3/f/93fcf1e7-e6a4-478b-96e7-d4b285925b00/vc_redist.x64.exe' FileName = 'vc_redist.x64.exe' } } } function Test-IsAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Resolve-PowerShellExe { $windowsPowerShell = Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe' if (Test-Path -LiteralPath $windowsPowerShell) { return $windowsPowerShell } $currentProcess = Get-Process -Id $PID return $currentProcess.Path } function ConvertTo-ArgumentText { param([string[]]$Arguments) return (($Arguments | ForEach-Object { if ($_ -match '\s|"' ) { '"' + ($_ -replace '"', '\"') + '"' } else { $_ } }) -join ' ') } function Test-IsNullOrWhiteSpace { param([string]$Value) if ($null -eq $Value) { return $true } return ($Value.Trim().Length -eq 0) } function Get-DefaultArchitecture { if ([Environment]::Is64BitOperatingSystem) { return 'x64' } return 'x86' } function Get-WindowsVersion { try { $currentVersion = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop $major = Get-PropertyValue -InputObject $currentVersion -Name 'CurrentMajorVersionNumber' $minor = Get-PropertyValue -InputObject $currentVersion -Name 'CurrentMinorVersionNumber' if (-not (Test-IsNullOrWhiteSpace $major)) { return New-Object System.Version -ArgumentList ([int]$major), ([int]$minor) } $versionText = Get-PropertyValue -InputObject $currentVersion -Name 'CurrentVersion' if (-not (Test-IsNullOrWhiteSpace $versionText)) { return [version]$versionText } } catch { return [Environment]::OSVersion.Version } return [Environment]::OSVersion.Version } function Test-IsLegacyWindowsForMatBao { $version = Get-WindowsVersion if ($version.Major -eq 6 -and $version.Minor -ge 1 -and $version.Minor -le 3) { return $true } return $false } function Get-WindowsDisplayName { try { $currentVersion = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop $productName = Get-PropertyValue -InputObject $currentVersion -Name 'ProductName' if (-not (Test-IsNullOrWhiteSpace $productName)) { return $productName } } catch { return "Windows $((Get-WindowsVersion).ToString())" } return "Windows $((Get-WindowsVersion).ToString())" } function Ensure-Administrator { if (Test-IsAdministrator) { return } $scriptPath = $PSCommandPath if ((Test-IsNullOrWhiteSpace $scriptPath) -or -not (Test-Path -LiteralPath $scriptPath)) { throw 'Cần lưu script thành file .ps1 trước khi chạy để có thể xin quyền Administrator.' } $arguments = @( '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $scriptPath ) if ($AutoInstall) { $arguments += '-AutoInstall' } if ($ReinstallX86) { $arguments += '-ReinstallX86' } if ($Architecture) { $arguments += @('-Architecture', $Architecture) } if ($SkipDotNet) { $arguments += '-SkipDotNet' } if ($SilentAppInstall) { $arguments += '-SilentAppInstall' } [System.Windows.Forms.MessageBox]::Show( 'Trình cài đặt cần quyền Administrator để cài .NET và Mắt Bão Invoice. Bấm OK để mở lại với quyền Administrator.', 'Trình cài đặt Mắt Bão Invoice', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) | Out-Null Start-Process -FilePath (Resolve-PowerShellExe) -ArgumentList (ConvertTo-ArgumentText $arguments) -Verb RunAs exit } function Add-InstallLog { param([string]$Message) $line = '[{0}] {1}' -f (Get-Date -Format 'HH:mm:ss'), $Message if ($script:LogBox -ne $null) { $script:LogBox.AppendText($line + [Environment]::NewLine) $script:LogBox.SelectionStart = $script:LogBox.TextLength $script:LogBox.ScrollToCaret() [System.Windows.Forms.Application]::DoEvents() } else { Write-Host $line } } function Get-DotNetRoot { param( [ValidateSet('x86', 'x64')] [string]$RuntimeArchitecture ) if ($RuntimeArchitecture -eq 'x64') { return (Join-Path $env:ProgramFiles 'dotnet') } $programFilesX86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)') if (-not (Test-IsNullOrWhiteSpace $programFilesX86)) { return (Join-Path $programFilesX86 'dotnet') } return (Join-Path $env:ProgramFiles 'dotnet') } function Add-VersionIfValid { param( [System.Collections.Generic.List[version]]$Versions, [string]$VersionText ) if (Test-IsNullOrWhiteSpace $VersionText) { return } try { $version = [version]$VersionText if ($version.Major -eq 6) { $Versions.Add($version) } } catch { return } } function Get-DotNetDesktopRuntime6 { param( [ValidateSet('x86', 'x64')] [string]$RuntimeArchitecture ) $versions = New-Object 'System.Collections.Generic.List[version]' $dotnetExe = Join-Path (Get-DotNetRoot -RuntimeArchitecture $RuntimeArchitecture) 'dotnet.exe' if (Test-Path -LiteralPath $dotnetExe) { try { $runtimeLines = & $dotnetExe --list-runtimes 2>$null foreach ($line in $runtimeLines) { if ($line -match '^Microsoft\.WindowsDesktop\.App\s+([0-9]+\.[0-9]+\.[0-9]+)') { Add-VersionIfValid -Versions $versions -VersionText $Matches[1] } } } catch { Add-InstallLog "Không đọc được runtime từ ${dotnetExe}: $($_.Exception.Message)" } } $registryPaths = @( "HKLM:\SOFTWARE\dotnet\Setup\InstalledVersions\$RuntimeArchitecture\sharedfx\Microsoft.WindowsDesktop.App" ) if ($RuntimeArchitecture -eq 'x86' -and [Environment]::Is64BitOperatingSystem) { $registryPaths += 'HKLM:\SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x86\sharedfx\Microsoft.WindowsDesktop.App' } foreach ($path in $registryPaths) { if (-not (Test-Path -LiteralPath $path)) { continue } try { $property = Get-ItemProperty -LiteralPath $path -ErrorAction SilentlyContinue if ($property -and $property.PSObject.Properties.Name -contains 'Version') { Add-VersionIfValid -Versions $versions -VersionText ([string]$property.Version) } foreach ($child in (Get-ChildItem -LiteralPath $path -ErrorAction SilentlyContinue)) { Add-VersionIfValid -Versions $versions -VersionText $child.PSChildName } } catch { Add-InstallLog "Không đọc được registry ${path}: $($_.Exception.Message)" } } $highest = $versions | Sort-Object -Descending | Select-Object -First 1 $result = New-Object PSObject $result | Add-Member -MemberType NoteProperty -Name Architecture -Value $RuntimeArchitecture $result | Add-Member -MemberType NoteProperty -Name Installed -Value ($null -ne $highest) $result | Add-Member -MemberType NoteProperty -Name Version -Value $(if ($null -ne $highest) { $highest.ToString() } else { '' }) return $result } function Save-Download { param( [string]$Url, [string]$OutputPath ) $directory = Split-Path -Parent $OutputPath if (-not (Test-Path -LiteralPath $directory)) { New-Item -Path $directory -ItemType Directory -Force | Out-Null } Add-InstallLog "Đang tải: $Url" try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { # Older Windows versions may not expose all TLS constants. } $webClient = New-Object System.Net.WebClient try { $webClient.DownloadFile($Url, $OutputPath) } finally { $webClient.Dispose() } if (-not (Test-Path -LiteralPath $OutputPath)) { throw "Tải file thất bại: $Url" } $fileInfo = Get-Item -LiteralPath $OutputPath if ($fileInfo.Length -le 0) { throw "File tải về rỗng: $OutputPath" } Add-InstallLog ("Đã tải xong: {0:N1} MB" -f ($fileInfo.Length / 1MB)) } function Expand-ZipFile { param( [string]$ZipPath, [string]$DestinationPath ) if (-not (Test-Path -LiteralPath $DestinationPath)) { New-Item -Path $DestinationPath -ItemType Directory -Force | Out-Null } if (Get-Command Expand-Archive -ErrorAction SilentlyContinue) { Expand-Archive -LiteralPath $ZipPath -DestinationPath $DestinationPath -Force return } try { Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop [System.IO.Compression.ZipFile]::ExtractToDirectory($ZipPath, $DestinationPath) return } catch { Add-InstallLog "Không dùng được .NET ZipFile, chuyển sang Shell unzip: $($_.Exception.Message)" } $shell = New-Object -ComObject Shell.Application $zipNamespace = $shell.NameSpace($ZipPath) $destinationNamespace = $shell.NameSpace($DestinationPath) if ($null -eq $zipNamespace -or $null -eq $destinationNamespace) { throw "Không giải nén được file ZIP: $ZipPath" } $destinationNamespace.CopyHere($zipNamespace.Items(), 0x14) Start-Sleep -Seconds 3 } function Invoke-Installer { param( [string]$FilePath, [string[]]$Arguments, [string]$DisplayName, [int[]]$SuccessExitCodes = @(0, 1641, 3010) ) Add-InstallLog "Đang cài: $DisplayName" $process = Start-Process -FilePath $FilePath -ArgumentList $Arguments -Wait -PassThru Add-InstallLog "$DisplayName trả về mã thoát $($process.ExitCode)" if ($SuccessExitCodes -notcontains $process.ExitCode) { throw "$DisplayName cài thất bại. Mã thoát: $($process.ExitCode)" } if (@(1641, 3010) -contains $process.ExitCode) { Add-InstallLog "$DisplayName yêu cầu khởi động lại máy sau khi cài xong." } } function Get-PropertyValue { param( [object]$InputObject, [string]$Name ) if ($null -eq $InputObject) { return '' } $property = $InputObject.PSObject.Properties[$Name] if ($null -eq $property) { return '' } return [string]$property.Value } function Get-MatBaoInvoiceInstallations { $uninstallRoots = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' ) $items = New-Object System.Collections.Generic.List[object] foreach ($root in $uninstallRoots) { if (-not (Test-Path -LiteralPath $root)) { continue } foreach ($key in (Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) { try { $property = Get-ItemProperty -LiteralPath $key.PSPath -ErrorAction Stop $displayName = Get-PropertyValue -InputObject $property -Name 'DisplayName' $displayVersion = Get-PropertyValue -InputObject $property -Name 'DisplayVersion' $publisher = Get-PropertyValue -InputObject $property -Name 'Publisher' $uninstallString = Get-PropertyValue -InputObject $property -Name 'UninstallString' $quietUninstallString = Get-PropertyValue -InputObject $property -Name 'QuietUninstallString' $isMatBaoInvoice = $Config.App.ProductCodes -contains $key.PSChildName -or $displayName -like 'Matbao-invoice*' -or $displayName -like 'Mắt Bão Invoice*' -or $uninstallString -like "*$($Config.App.ProductCode)*" -or $uninstallString -like "*$($Config.App.Legacy.ProductCode)*" if (-not $isMatBaoInvoice) { continue } $item = New-Object PSObject $item | Add-Member -MemberType NoteProperty -Name DisplayName -Value $displayName $item | Add-Member -MemberType NoteProperty -Name DisplayVersion -Value $displayVersion $item | Add-Member -MemberType NoteProperty -Name Publisher -Value $publisher $item | Add-Member -MemberType NoteProperty -Name ProductCode -Value $(if ($key.PSChildName -match '^\{[0-9A-Fa-f-]{36}\}$') { $key.PSChildName } else { $Config.App.ProductCode }) $item | Add-Member -MemberType NoteProperty -Name RegistryKey -Value $key.PSPath $item | Add-Member -MemberType NoteProperty -Name UninstallString -Value $uninstallString $item | Add-Member -MemberType NoteProperty -Name QuietUninstallString -Value $quietUninstallString $items.Add($item) } catch { Add-InstallLog "Không đọc được khóa gỡ cài đặt $($key.Name): $($_.Exception.Message)" } } } return $items } function Uninstall-MatBaoInvoice { $installations = @(Get-MatBaoInvoiceInstallations) if ($installations.Count -eq 0) { Add-InstallLog 'Chưa phát hiện bản Mắt Bão Invoice đang cài. Vẫn kiểm tra mã cài đặt MSI để bảo đảm sạch.' } else { foreach ($item in $installations) { Add-InstallLog "Phát hiện bản đang cài: $($item.DisplayName) $($item.DisplayVersion)" } } $productCodes = @($installations | ForEach-Object { $_.ProductCode }) + @($Config.App.ProductCodes) $productCodes = $productCodes | Where-Object { -not (Test-IsNullOrWhiteSpace $_) } | Select-Object -Unique foreach ($productCode in $productCodes) { Add-InstallLog "Đang gỡ Mắt Bão Invoice: $productCode" $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList @('/x', $productCode, '/qn', '/norestart') -Wait -PassThru Add-InstallLog "Gỡ Mắt Bão Invoice trả về mã thoát $($process.ExitCode)" if (@(0, 1605, 1614, 1641, 3010) -notcontains $process.ExitCode) { throw "Gỡ Mắt Bão Invoice thất bại. Mã thoát: $($process.ExitCode)" } if (@(1605, 1614) -contains $process.ExitCode) { Add-InstallLog 'Không còn bản Mắt Bão Invoice tương ứng trong máy.' } elseif (@(1641, 3010) -contains $process.ExitCode) { Add-InstallLog 'Windows có thể yêu cầu khởi động lại máy sau khi gỡ/cài.' } } } function Reinstall-MatBaoInvoice32Bit { param( [bool]$InstallDotNetIfMissing, [bool]$UseSilentAppInstall ) if (Test-IsLegacyWindowsForMatBao) { Add-InstallLog 'Bắt đầu chế độ khắc phục trên Windows 7/8: cài lại bản Mắt Bão Invoice 2.0 tương thích.' Install-MatBaoInvoiceLegacy ` -InstallDotNetIfMissing $InstallDotNetIfMissing ` -UseSilentAppInstall $UseSilentAppInstall return } Add-InstallLog 'Bắt đầu chế độ khắc phục: gỡ bản hiện tại và cài lại Mắt Bão 32 bit.' Uninstall-MatBaoInvoice Install-MatBaoInvoice ` -AppArchitecture x86 ` -InstallDotNetIfMissing $InstallDotNetIfMissing ` -UseSilentAppInstall $UseSilentAppInstall } function Get-NetFramework4Release { $paths = @( 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\NET Framework Setup\NDP\v4\Full' ) foreach ($path in $paths) { if (-not (Test-Path -LiteralPath $path)) { continue } try { $property = Get-ItemProperty -LiteralPath $path -ErrorAction Stop $release = Get-PropertyValue -InputObject $property -Name 'Release' if (-not (Test-IsNullOrWhiteSpace $release)) { return [int]$release } } catch { Add-InstallLog "Không đọc được .NET Framework registry ${path}: $($_.Exception.Message)" } } return 0 } function Test-NetFramework46OrNewer { return ((Get-NetFramework4Release) -ge [int]$Config.DotNet.Framework46.MinimumRelease) } function Install-NetFramework46 { param([string]$WorkingDirectory) if (Test-NetFramework46OrNewer) { Add-InstallLog '.NET Framework 4.6 hoặc mới hơn đã có sẵn.' return } $frameworkFile = Join-Path $WorkingDirectory $Config.DotNet.Framework46.FileName Save-Download -Url $Config.DotNet.Framework46.Url -OutputPath $frameworkFile Invoke-Installer ` -FilePath $frameworkFile ` -Arguments @('/q', '/norestart') ` -DisplayName $Config.DotNet.Framework46.Name if (-not (Test-NetFramework46OrNewer)) { throw '.NET Framework 4.6 vừa cài nhưng chưa xác nhận được trong máy.' } Add-InstallLog '.NET Framework 4.6 đã sẵn sàng.' } function Test-VisualCpp2015Runtime { param( [ValidateSet('x86', 'x64')] [string]$RuntimeArchitecture ) $paths = @( "HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\$RuntimeArchitecture", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\$RuntimeArchitecture" ) foreach ($path in $paths) { if (-not (Test-Path -LiteralPath $path)) { continue } try { $property = Get-ItemProperty -LiteralPath $path -ErrorAction Stop $installed = Get-PropertyValue -InputObject $property -Name 'Installed' if ($installed -eq '1') { return $true } } catch { Add-InstallLog "Không đọc được Visual C++ runtime registry ${path}: $($_.Exception.Message)" } } return $false } function Install-VisualCpp2015Runtime { param( [ValidateSet('x86', 'x64')] [string]$RuntimeArchitecture, [string]$WorkingDirectory ) if (Test-VisualCpp2015Runtime -RuntimeArchitecture $RuntimeArchitecture) { Add-InstallLog "$($Config.VisualCpp[$RuntimeArchitecture].Name) đã có sẵn." return } $runtimeFile = Join-Path $WorkingDirectory $Config.VisualCpp[$RuntimeArchitecture].FileName Save-Download -Url $Config.VisualCpp[$RuntimeArchitecture].Url -OutputPath $runtimeFile Invoke-Installer ` -FilePath $runtimeFile ` -Arguments @('/install', '/quiet', '/norestart') ` -DisplayName $Config.VisualCpp[$RuntimeArchitecture].Name ` -SuccessExitCodes @(0, 1638, 1641, 3010) } function Set-MatBaoLegacyCompatibility { $installDirectory = $Config.App.Legacy.InstallDirectory $exePath = Join-Path $installDirectory $Config.App.Legacy.ExeName $configPath = Join-Path $installDirectory $Config.App.Legacy.ConfigName if (Test-Path -LiteralPath $configPath) { $configText = [System.IO.File]::ReadAllText($configPath) $updatedText = $configText.Replace('sha256', 'sha1').Replace('SHA256', 'SHA1') if ($updatedText -ne $configText) { [System.IO.File]::WriteAllText($configPath, $updatedText) Add-InstallLog 'Đã chỉnh cấu hình Win7/Win8: sha256 -> sha1.' } else { Add-InstallLog 'Cấu hình Win7/Win8 đã ở trạng thái phù hợp.' } } else { Add-InstallLog "Không tìm thấy file cấu hình legacy: $configPath" } if (Test-Path -LiteralPath $exePath) { $layersPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers' if (-not (Test-Path -LiteralPath $layersPath)) { New-Item -Path $layersPath -Force | Out-Null } New-ItemProperty -LiteralPath $layersPath -Name $exePath -Value '~ RUNASADMIN' -PropertyType String -Force | Out-Null Add-InstallLog 'Đã thiết lập ứng dụng luôn chạy bằng quyền Administrator.' } else { Add-InstallLog "Không tìm thấy file chạy legacy: $exePath" } } function Install-MatBaoInvoiceLegacy { param( [bool]$InstallDotNetIfMissing, [bool]$UseSilentAppInstall ) $workRoot = Join-Path $env:TEMP ('MatBaoInvoiceLegacyInstall-' + [guid]::NewGuid().ToString('N')) $extractRoot = Join-Path $workRoot 'extract' New-Item -Path $extractRoot -ItemType Directory -Force | Out-Null try { Add-InstallLog 'Phát hiện Windows 7/8/8.1, chuyển sang bộ cài Mắt Bão Invoice 2.0.' Add-InstallLog "Thư mục tạm: $workRoot" Uninstall-MatBaoInvoice if ($InstallDotNetIfMissing) { Install-NetFramework46 -WorkingDirectory $workRoot Install-VisualCpp2015Runtime -RuntimeArchitecture x86 -WorkingDirectory $workRoot if ([Environment]::Is64BitOperatingSystem) { Install-VisualCpp2015Runtime -RuntimeArchitecture x64 -WorkingDirectory $workRoot } } else { Add-InstallLog 'Bỏ qua bước cài runtime nền theo lựa chọn của người dùng.' } $appZip = Join-Path $workRoot $Config.App.Legacy.FileName Save-Download -Url $Config.App.Legacy.Url -OutputPath $appZip Add-InstallLog 'Đang giải nén bộ cài Mắt Bão Invoice 2.0...' Expand-ZipFile -ZipPath $appZip -DestinationPath $extractRoot $msi = Get-ChildItem -Path $extractRoot -Recurse -Filter '*.msi' | Select-Object -First 1 if ($null -eq $msi) { throw 'Không tìm thấy file MSI trong gói ZIP Mắt Bão Invoice 2.0.' } $msiArguments = @('/i', ('"{0}"' -f $msi.FullName), '/norestart') if ($UseSilentAppInstall) { $msiArguments += '/qn' } Invoke-Installer ` -FilePath 'msiexec.exe' ` -Arguments $msiArguments ` -DisplayName $Config.App.Legacy.Name Set-MatBaoLegacyCompatibility Add-InstallLog 'Hoàn tất cài đặt Mắt Bão Invoice 2.0 cho Windows 7/8.' } finally { if (Test-Path -LiteralPath $workRoot) { try { Remove-Item -LiteralPath $workRoot -Recurse -Force } catch { Add-InstallLog "Không xóa được thư mục tạm ${workRoot}: $($_.Exception.Message)" } } } } function Install-DotNetDesktopRuntime6 { param( [ValidateSet('x86', 'x64')] [string]$RuntimeArchitecture, [string]$WorkingDirectory ) $current = Get-DotNetDesktopRuntime6 -RuntimeArchitecture $RuntimeArchitecture if ($current.Installed) { Add-InstallLog "$($Config.DotNet[$RuntimeArchitecture].Name) đã có sẵn: $($current.Version)" return } $dotnetFile = Join-Path $WorkingDirectory $Config.DotNet[$RuntimeArchitecture].FileName Save-Download -Url $Config.DotNet[$RuntimeArchitecture].Url -OutputPath $dotnetFile Invoke-Installer ` -FilePath $dotnetFile ` -Arguments @('/install', '/quiet', '/norestart') ` -DisplayName $Config.DotNet[$RuntimeArchitecture].Name $afterInstall = Get-DotNetDesktopRuntime6 -RuntimeArchitecture $RuntimeArchitecture if (-not $afterInstall.Installed) { throw "$($Config.DotNet[$RuntimeArchitecture].Name) vừa cài nhưng chưa xác nhận được trong máy." } Add-InstallLog "$($Config.DotNet[$RuntimeArchitecture].Name) đã sẵn sàng: $($afterInstall.Version)" } function Install-MatBaoInvoice { param( [ValidateSet('x86', 'x64')] [string]$AppArchitecture, [bool]$InstallDotNetIfMissing, [bool]$UseSilentAppInstall ) if (Test-IsLegacyWindowsForMatBao) { Install-MatBaoInvoiceLegacy ` -InstallDotNetIfMissing $InstallDotNetIfMissing ` -UseSilentAppInstall $UseSilentAppInstall return } if ($AppArchitecture -eq 'x64' -and -not [Environment]::Is64BitOperatingSystem) { throw 'Windows 32 bit không cài được bản Mắt Bão 64 bit.' } $workRoot = Join-Path $env:TEMP ('MatBaoInvoiceInstall-' + [guid]::NewGuid().ToString('N')) $extractRoot = Join-Path $workRoot 'extract' New-Item -Path $extractRoot -ItemType Directory -Force | Out-Null try { Add-InstallLog "Thư mục tạm: $workRoot" if ($InstallDotNetIfMissing) { Install-DotNetDesktopRuntime6 -RuntimeArchitecture $AppArchitecture -WorkingDirectory $workRoot } else { Add-InstallLog 'Bỏ qua bước cài .NET theo lựa chọn của người dùng.' } $appZip = Join-Path $workRoot $Config.App[$AppArchitecture].FileName Save-Download -Url $Config.App[$AppArchitecture].Url -OutputPath $appZip Add-InstallLog 'Đang giải nén bộ cài Mắt Bão Invoice...' Expand-ZipFile -ZipPath $appZip -DestinationPath $extractRoot $msi = Get-ChildItem -Path $extractRoot -Recurse -Filter '*.msi' | Where-Object { $_.Name -like '*SignatureMatBaoInvoice*' } | Select-Object -First 1 if ($null -eq $msi) { $msi = Get-ChildItem -Path $extractRoot -Recurse -Filter '*.msi' | Select-Object -First 1 } if ($null -eq $msi) { throw 'Không tìm thấy file MSI trong gói ZIP Mắt Bão Invoice.' } Add-InstallLog "Tìm thấy MSI: $($msi.Name)" $msiArguments = @('/i', ('"{0}"' -f $msi.FullName), '/norestart') if ($UseSilentAppInstall) { $msiArguments += '/qn' } Invoke-Installer ` -FilePath 'msiexec.exe' ` -Arguments $msiArguments ` -DisplayName $Config.App[$AppArchitecture].Name Add-InstallLog 'Hoàn tất cài đặt Mắt Bão Invoice.' } finally { if (Test-Path -LiteralPath $workRoot) { try { Remove-Item -LiteralPath $workRoot -Recurse -Force } catch { Add-InstallLog "Không xóa được thư mục tạm ${workRoot}: $($_.Exception.Message)" } } } } function Get-BrandLogoImage { try { $logoPath = Join-Path $env:TEMP $Config.Brand.LogoFileName $needsDownload = $true if (Test-Path -LiteralPath $logoPath) { $logoFile = Get-Item -LiteralPath $logoPath $needsDownload = $logoFile.Length -le 0 } if ($needsDownload) { try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { # Older Windows versions may not expose all TLS constants. } $webClient = New-Object System.Net.WebClient try { $webClient.DownloadFile($Config.Brand.LogoUrl, $logoPath) } finally { $webClient.Dispose() } } if (-not (Test-Path -LiteralPath $logoPath)) { return $null } $bytes = [System.IO.File]::ReadAllBytes($logoPath) $stream = New-Object System.IO.MemoryStream $stream.Write($bytes, 0, $bytes.Length) $stream.Position = 0 $image = [System.Drawing.Image]::FromStream($stream) $bitmap = New-Object System.Drawing.Bitmap $image $image.Dispose() $stream.Dispose() return $bitmap } catch { return $null } } function New-UiLabel { param( [string]$Text, [int]$X, [int]$Y, [int]$Width, [int]$Height, [System.Drawing.Font]$Font, [System.Drawing.Color]$ForeColor ) $label = New-Object System.Windows.Forms.Label $label.Text = $Text $label.Location = New-Object System.Drawing.Point($X, $Y) $label.Size = New-Object System.Drawing.Size($Width, $Height) $label.Font = $Font $label.ForeColor = $ForeColor $label.BackColor = [System.Drawing.Color]::Transparent $label.AutoEllipsis = $true return $label } function New-CardPanel { param( [int]$X, [int]$Y, [int]$Width, [int]$Height ) $panel = New-Object System.Windows.Forms.Panel $panel.Location = New-Object System.Drawing.Point($X, $Y) $panel.Size = New-Object System.Drawing.Size($Width, $Height) $panel.BackColor = [System.Drawing.Color]::White $panel.BorderStyle = [System.Windows.Forms.BorderStyle]::None $panel.Tag = [System.Drawing.Color]::FromArgb(229, 231, 235) $panel.Add_Paint({ param($sender, $eventArgs) [System.Windows.Forms.ControlPaint]::DrawBorder( $eventArgs.Graphics, $sender.ClientRectangle, $sender.Tag, [System.Windows.Forms.ButtonBorderStyle]::Solid ) }) return $panel } function Set-FlatButtonStyle { param( [System.Windows.Forms.Button]$Button, [System.Drawing.Color]$BackColor, [System.Drawing.Color]$ForeColor, [bool]$Bold = $false ) $Button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat $Button.FlatAppearance.BorderSize = 0 $Button.BackColor = $BackColor $Button.ForeColor = $ForeColor $Button.Cursor = [System.Windows.Forms.Cursors]::Hand $fontStyle = if ($Bold) { [System.Drawing.FontStyle]::Bold } else { [System.Drawing.FontStyle]::Regular } $Button.Font = New-Object System.Drawing.Font('Segoe UI', 9, $fontStyle) } function Set-StatusLabel { param( [System.Windows.Forms.Label]$Label, [string]$Text, [ValidateSet('Success', 'Warning', 'Muted')] [string]$State ) $Label.Text = $Text switch ($State) { 'Success' { $Label.ForeColor = [System.Drawing.Color]::FromArgb(22, 101, 52) $Label.BackColor = [System.Drawing.Color]::FromArgb(220, 252, 231) } 'Warning' { $Label.ForeColor = [System.Drawing.Color]::FromArgb(146, 64, 14) $Label.BackColor = [System.Drawing.Color]::FromArgb(254, 243, 199) } default { $Label.ForeColor = [System.Drawing.Color]::FromArgb(75, 85, 99) $Label.BackColor = [System.Drawing.Color]::FromArgb(243, 244, 246) } } } function Get-StatusText { param( [ValidateSet('x86', 'x64')] [string]$StatusArchitecture ) if (Test-IsLegacyWindowsForMatBao) { $frameworkText = if (Test-NetFramework46OrNewer) { 'Đã có' } else { 'Chưa có' } $osText = Get-WindowsDisplayName return @( "Windows: $osText", 'Phiên bản cài: Mắt Bão Invoice 2.0 cho Windows 7/8', ".NET Framework 4.6: $frameworkText" ) -join [Environment]::NewLine } if (Test-IsNullOrWhiteSpace $StatusArchitecture) { $StatusArchitecture = Get-DefaultArchitecture } if ($StatusArchitecture -eq 'x64' -and -not [Environment]::Is64BitOperatingSystem) { throw 'Windows 32 bit không cài được bản Mắt Bão 64 bit.' } $runtime = Get-DotNetDesktopRuntime6 -RuntimeArchitecture $StatusArchitecture $runtimeText = if ($runtime.Installed) { "Đã có $($runtime.Version)" } else { 'Chưa có' } $osText = if ([Environment]::Is64BitOperatingSystem) { '64 bit' } else { '32 bit' } $appText = if ($StatusArchitecture -eq 'x64') { 'Mắt Bão 64 bit' } else { 'Mắt Bão 32 bit' } return @( "Windows: $osText", "Phiên bản cài: $appText", ".NET 6 Desktop Runtime cần dùng ($StatusArchitecture): $runtimeText" ) -join [Environment]::NewLine } function Show-MainForm { [System.Windows.Forms.Application]::EnableVisualStyles() try { [System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false) } catch { # Ignore if WinForms has already initialized text rendering. } $colorBackground = [System.Drawing.Color]::FromArgb(245, 247, 250) $colorPrimary = [System.Drawing.Color]::FromArgb(0, 94, 184) $colorPrimaryDark = [System.Drawing.Color]::FromArgb(0, 65, 128) $colorWarningAction = [System.Drawing.Color]::FromArgb(217, 119, 6) $colorText = [System.Drawing.Color]::FromArgb(17, 24, 39) $colorMuted = [System.Drawing.Color]::FromArgb(75, 85, 99) $colorLine = [System.Drawing.Color]::FromArgb(229, 231, 235) $fontTitle = New-Object System.Drawing.Font('Segoe UI', 16, [System.Drawing.FontStyle]::Bold) $fontSection = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold) $fontBody = New-Object System.Drawing.Font('Segoe UI', 9) $fontSmall = New-Object System.Drawing.Font('Segoe UI', 8.5) $fontMono = New-Object System.Drawing.Font('Consolas', 9) $isLegacyWindows = Test-IsLegacyWindowsForMatBao $form = New-Object System.Windows.Forms.Form $form.Text = 'Trình cài đặt Mắt Bão Invoice' $form.StartPosition = 'CenterScreen' $form.ClientSize = New-Object System.Drawing.Size(860, 760) $form.MinimumSize = New-Object System.Drawing.Size(876, 799) $form.BackColor = $colorBackground $form.Font = $fontBody $header = New-Object System.Windows.Forms.Panel $header.Location = New-Object System.Drawing.Point(0, 0) $header.Size = New-Object System.Drawing.Size(860, 122) $header.BackColor = [System.Drawing.Color]::White $header.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right $form.Controls.Add($header) $logo = Get-BrandLogoImage if ($logo -ne $null) { $logoBox = New-Object System.Windows.Forms.PictureBox $logoBox.Image = $logo $logoBox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom $logoBox.Location = New-Object System.Drawing.Point(28, 28) $logoBox.Size = New-Object System.Drawing.Size(156, 44) $header.Controls.Add($logoBox) } else { $brandFallback = New-UiLabel -Text 'MẮT BÃO' -X 30 -Y 30 -Width 150 -Height 36 -Font (New-Object System.Drawing.Font('Segoe UI', 15, [System.Drawing.FontStyle]::Bold)) -ForeColor $colorPrimary $header.Controls.Add($brandFallback) } $title = New-UiLabel -Text 'Cài đặt ký số USB Token' -X 210 -Y 24 -Width 420 -Height 30 -Font $fontTitle -ForeColor $colorText $header.Controls.Add($title) $subtitleText = if ($isLegacyWindows) { 'Tự nhận Windows 7/8, cài bản Mắt Bão Invoice 2.0 và cấu hình tương thích ký số.' } else { 'Tự nhận phiên bản Windows, kiểm tra .NET 6 và cài Mắt Bão Invoice trong một lần chạy.' } $subtitle = New-UiLabel -Text $subtitleText -X 212 -Y 62 -Width 560 -Height 24 -Font $fontBody -ForeColor $colorMuted $header.Controls.Add($subtitle) $selectedInstallBadge = New-Object System.Windows.Forms.Label $selectedInstallBadge.Location = New-Object System.Drawing.Point(696, 32) $selectedInstallBadge.Size = New-Object System.Drawing.Size(130, 30) $selectedInstallBadge.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $selectedInstallBadge.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold) $selectedInstallBadge.ForeColor = $colorPrimaryDark $selectedInstallBadge.BackColor = [System.Drawing.Color]::FromArgb(219, 234, 254) $header.Controls.Add($selectedInstallBadge) $divider = New-Object System.Windows.Forms.Panel $divider.Location = New-Object System.Drawing.Point(0, 121) $divider.Size = New-Object System.Drawing.Size(860, 1) $divider.BackColor = $colorLine $divider.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right $header.Controls.Add($divider) $versionCard = New-CardPanel -X 28 -Y 146 -Width 390 -Height 160 $form.Controls.Add($versionCard) $versionTitle = New-UiLabel -Text 'Phiên bản cài đặt' -X 20 -Y 18 -Width 220 -Height 24 -Font $fontSection -ForeColor $colorText $versionCard.Controls.Add($versionTitle) $versionHelpText = if ($isLegacyWindows) { 'Windows 7/8 sẽ tự dùng bộ cài 2.0 theo hướng dẫn Mắt Bão.' } else { 'Khuyến nghị giữ tự động để cài đúng bản theo Windows hiện tại.' } $versionHelp = New-UiLabel -Text $versionHelpText -X 20 -Y 43 -Width 340 -Height 22 -Font $fontSmall -ForeColor $colorMuted $versionCard.Controls.Add($versionHelp) $defaultArchitecture = Get-DefaultArchitecture $radioAuto = New-Object System.Windows.Forms.RadioButton $radioAuto.Text = if ($isLegacyWindows) { 'Tự động cho Windows 7/8' } else { "Tự động theo máy này ($defaultArchitecture)" } $radioAuto.Location = New-Object System.Drawing.Point(22, 76) $radioAuto.Size = New-Object System.Drawing.Size(310, 24) $radioAuto.Font = $fontBody $radioAuto.Checked = $true $versionCard.Controls.Add($radioAuto) $radioX64 = New-Object System.Windows.Forms.RadioButton $radioX64.Text = '64 bit' $radioX64.Location = New-Object System.Drawing.Point(22, 108) $radioX64.Size = New-Object System.Drawing.Size(260, 24) $radioX64.Font = $fontBody $radioX64.Enabled = ([Environment]::Is64BitOperatingSystem -and -not $isLegacyWindows) $versionCard.Controls.Add($radioX64) $radioX86 = New-Object System.Windows.Forms.RadioButton $radioX86.Text = '32 bit' $radioX86.Location = New-Object System.Drawing.Point(112, 108) $radioX86.Size = New-Object System.Drawing.Size(260, 24) $radioX86.Font = $fontBody $radioX86.Enabled = -not $isLegacyWindows $versionCard.Controls.Add($radioX86) $optionsCard = New-CardPanel -X 442 -Y 146 -Width 390 -Height 160 $form.Controls.Add($optionsCard) $optionsTitle = New-UiLabel -Text 'Tùy chọn cài đặt' -X 20 -Y 18 -Width 220 -Height 24 -Font $fontSection -ForeColor $colorText $optionsCard.Controls.Add($optionsTitle) $optionsHelp = New-UiLabel -Text 'Các tùy chọn này giúp thao tác gọn hơn cho người dùng cuối.' -X 20 -Y 43 -Width 330 -Height 22 -Font $fontSmall -ForeColor $colorMuted $optionsCard.Controls.Add($optionsHelp) $checkDotNet = New-Object System.Windows.Forms.CheckBox $checkDotNet.Text = 'Tự cài .NET 6 Desktop Runtime nếu thiếu' $checkDotNet.Location = New-Object System.Drawing.Point(22, 78) $checkDotNet.Size = New-Object System.Drawing.Size(330, 24) $checkDotNet.Font = $fontBody $checkDotNet.Checked = $true $optionsCard.Controls.Add($checkDotNet) $checkSilent = New-Object System.Windows.Forms.CheckBox $checkSilent.Text = 'Cài Mắt Bão ở chế độ im lặng nếu có thể' $checkSilent.Location = New-Object System.Drawing.Point(22, 110) $checkSilent.Size = New-Object System.Drawing.Size(330, 24) $checkSilent.Font = $fontBody $checkSilent.Checked = $true $optionsCard.Controls.Add($checkSilent) $fixCard = New-CardPanel -X 28 -Y 326 -Width 804 -Height 88 $form.Controls.Add($fixCard) $fixTitle = New-UiLabel -Text 'Khắc phục lỗi ký số' -X 20 -Y 16 -Width 200 -Height 24 -Font $fontSection -ForeColor $colorText $fixCard.Controls.Add($fixTitle) $fixHelpText = if ($isLegacyWindows) { 'Trên Windows 7/8, trình cài đặt sẽ tự dùng bản 2.0, chỉnh sha1 và cấp quyền Administrator.' } else { 'Nếu máy 64-bit cài bản 64-bit nhưng ký số báo lỗi, hãy chuyển nhanh sang bản 32-bit tương thích hơn.' } $fixHelp = New-UiLabel -Text $fixHelpText -X 20 -Y 43 -Width 550 -Height 22 -Font $fontSmall -ForeColor $colorMuted $fixCard.Controls.Add($fixHelp) $buttonReinstallX86 = New-Object System.Windows.Forms.Button $buttonReinstallX86.Text = if ($isLegacyWindows) { 'Đã tự tối ưu' } else { 'Cài lại bản 32 bit' } $buttonReinstallX86.Location = New-Object System.Drawing.Point(604, 28) $buttonReinstallX86.Size = New-Object System.Drawing.Size(178, 34) Set-FlatButtonStyle -Button $buttonReinstallX86 -BackColor $colorWarningAction -ForeColor ([System.Drawing.Color]::White) -Bold $true $buttonReinstallX86.Enabled = -not $isLegacyWindows $fixCard.Controls.Add($buttonReinstallX86) $statusCard = New-CardPanel -X 28 -Y 434 -Width 804 -Height 124 $form.Controls.Add($statusCard) $statusTitle = New-UiLabel -Text 'Trạng thái máy' -X 20 -Y 16 -Width 180 -Height 24 -Font $fontSection -ForeColor $colorText $statusCard.Controls.Add($statusTitle) $osLabel = New-UiLabel -Text 'Windows' -X 20 -Y 52 -Width 160 -Height 20 -Font $fontSmall -ForeColor $colorMuted $statusCard.Controls.Add($osLabel) $dotnetX86Label = New-UiLabel -Text 'Phiên bản cài' -X 280 -Y 52 -Width 160 -Height 20 -Font $fontSmall -ForeColor $colorMuted $statusCard.Controls.Add($dotnetX86Label) $dotnetX64Label = New-UiLabel -Text '.NET cần dùng' -X 540 -Y 52 -Width 180 -Height 20 -Font $fontSmall -ForeColor $colorMuted $statusCard.Controls.Add($dotnetX64Label) $osValue = New-Object System.Windows.Forms.Label $osValue.Location = New-Object System.Drawing.Point(20, 76) $osValue.Size = New-Object System.Drawing.Size(210, 28) $osValue.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $osValue.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold) $statusCard.Controls.Add($osValue) $dotnetX86Value = New-Object System.Windows.Forms.Label $dotnetX86Value.Location = New-Object System.Drawing.Point(280, 76) $dotnetX86Value.Size = New-Object System.Drawing.Size(210, 28) $dotnetX86Value.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $dotnetX86Value.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold) $statusCard.Controls.Add($dotnetX86Value) $dotnetX64Value = New-Object System.Windows.Forms.Label $dotnetX64Value.Location = New-Object System.Drawing.Point(540, 76) $dotnetX64Value.Size = New-Object System.Drawing.Size(210, 28) $dotnetX64Value.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $dotnetX64Value.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold) $statusCard.Controls.Add($dotnetX64Value) $logCard = New-CardPanel -X 28 -Y 578 -Width 804 -Height 118 $form.Controls.Add($logCard) $logTitle = New-UiLabel -Text 'Nhật ký cài đặt' -X 20 -Y 14 -Width 180 -Height 24 -Font $fontSection -ForeColor $colorText $logCard.Controls.Add($logTitle) $script:LogBox = New-Object System.Windows.Forms.TextBox $script:LogBox.Location = New-Object System.Drawing.Point(20, 42) $script:LogBox.Size = New-Object System.Drawing.Size(762, 58) $script:LogBox.Multiline = $true $script:LogBox.ReadOnly = $true $script:LogBox.ScrollBars = 'Vertical' $script:LogBox.BorderStyle = [System.Windows.Forms.BorderStyle]::None $script:LogBox.BackColor = [System.Drawing.Color]::White $script:LogBox.Font = $fontMono $script:LogBox.HideSelection = $true $logCard.Controls.Add($script:LogBox) $progress = New-Object System.Windows.Forms.ProgressBar $progress.Location = New-Object System.Drawing.Point(28, 706) $progress.Size = New-Object System.Drawing.Size(804, 8) $progress.Style = 'Continuous' $form.Controls.Add($progress) $actionLabel = New-UiLabel -Text 'Sẵn sàng cài đặt.' -X 30 -Y 728 -Width 360 -Height 22 -Font $fontSmall -ForeColor $colorMuted $form.Controls.Add($actionLabel) $buttonRefresh = New-Object System.Windows.Forms.Button $buttonRefresh.Text = 'Kiểm tra lại' $buttonRefresh.Location = New-Object System.Drawing.Point(492, 720) $buttonRefresh.Size = New-Object System.Drawing.Size(106, 32) Set-FlatButtonStyle -Button $buttonRefresh -BackColor ([System.Drawing.Color]::FromArgb(229, 231, 235)) -ForeColor $colorText $form.Controls.Add($buttonRefresh) $buttonInstall = New-Object System.Windows.Forms.Button $buttonInstall.Text = 'Cài đặt ngay' $buttonInstall.Location = New-Object System.Drawing.Point(610, 720) $buttonInstall.Size = New-Object System.Drawing.Size(116, 32) Set-FlatButtonStyle -Button $buttonInstall -BackColor $colorPrimary -ForeColor ([System.Drawing.Color]::White) -Bold $true $form.Controls.Add($buttonInstall) $buttonClose = New-Object System.Windows.Forms.Button $buttonClose.Text = 'Đóng' $buttonClose.Location = New-Object System.Drawing.Point(738, 720) $buttonClose.Size = New-Object System.Drawing.Size(94, 32) Set-FlatButtonStyle -Button $buttonClose -BackColor ([System.Drawing.Color]::White) -ForeColor $colorText $buttonClose.FlatAppearance.BorderSize = 1 $buttonClose.FlatAppearance.BorderColor = $colorLine $form.Controls.Add($buttonClose) $form.AcceptButton = $buttonInstall $form.CancelButton = $buttonClose $refreshStatus = { try { $osText = if ($isLegacyWindows) { Get-WindowsDisplayName } elseif ([Environment]::Is64BitOperatingSystem) { 'Windows 64 bit' } else { 'Windows 32 bit' } Set-StatusLabel -Label $osValue -Text $osText -State 'Muted' $selectedArchitecture = & $getSelectedArchitecture $selectedVersionText = if ($isLegacyWindows) { 'Mắt Bão 2.0' } elseif ($selectedArchitecture -eq 'x64') { 'Mắt Bão 64 bit' } else { 'Mắt Bão 32 bit' } Set-StatusLabel -Label $dotnetX86Value -Text $selectedVersionText -State 'Muted' if ($isLegacyWindows) { $dotnetX64Label.Text = '.NET Framework 4.6' if (Test-NetFramework46OrNewer) { Set-StatusLabel -Label $dotnetX64Value -Text 'Đã có' -State 'Success' } else { if ($checkDotNet.Checked) { Set-StatusLabel -Label $dotnetX64Value -Text 'Thiếu, sẽ tự cài' -State 'Warning' } else { Set-StatusLabel -Label $dotnetX64Value -Text 'Chưa có' -State 'Warning' } } } else { $dotnetX64Label.Text = ".NET cần dùng ($selectedArchitecture)" $runtime = Get-DotNetDesktopRuntime6 -RuntimeArchitecture $selectedArchitecture if ($runtime.Installed) { Set-StatusLabel -Label $dotnetX64Value -Text "Đã có $($runtime.Version)" -State 'Success' } else { if ($checkDotNet.Checked) { Set-StatusLabel -Label $dotnetX64Value -Text 'Thiếu, sẽ tự cài' -State 'Warning' } else { Set-StatusLabel -Label $dotnetX64Value -Text 'Chưa có' -State 'Warning' } } } $actionLabel.Text = 'Đã kiểm tra trạng thái máy.' } catch { $actionLabel.Text = "Không kiểm tra được trạng thái .NET: $($_.Exception.Message)" } } $getSelectedArchitecture = { if ($isLegacyWindows) { return 'x86' } if ($radioAuto.Checked) { return Get-DefaultArchitecture } if ($radioX64.Checked) { return 'x64' } return 'x86' } $updateSelection = { $selectedArchitecture = & $getSelectedArchitecture if ($isLegacyWindows) { $selectedInstallBadge.Text = 'Win7/8: v2.0' } elseif ($radioAuto.Checked) { $selectedInstallBadge.Text = "Tự động: $selectedArchitecture" } else { $selectedInstallBadge.Text = "Đã chọn: $selectedArchitecture" } if ($null -ne $dotnetX86Value -and $null -ne $dotnetX64Value) { & $refreshStatus } } $radioAuto.Add_CheckedChanged($updateSelection) $radioX64.Add_CheckedChanged($updateSelection) $radioX86.Add_CheckedChanged($updateSelection) $checkDotNet.Add_CheckedChanged($refreshStatus) $buttonRefresh.Add_Click($refreshStatus) $buttonClose.Add_Click({ $form.Close() }) $buttonInstall.Add_Click({ $buttonInstall.Enabled = $false $buttonReinstallX86.Enabled = $false $buttonRefresh.Enabled = $false $buttonClose.Enabled = $false $progress.Style = 'Marquee' $actionLabel.Text = 'Đang xử lý, vui lòng chờ...' try { $selectedArchitecture = & $getSelectedArchitecture if ($radioAuto.Checked) { Add-InstallLog "Bắt đầu cài đặt tự động theo máy này: $selectedArchitecture" } else { Add-InstallLog "Bắt đầu cài đặt bản $selectedArchitecture" } Install-MatBaoInvoice ` -AppArchitecture $selectedArchitecture ` -InstallDotNetIfMissing $checkDotNet.Checked ` -UseSilentAppInstall $checkSilent.Checked & $refreshStatus $actionLabel.Text = 'Cài đặt hoàn tất.' [System.Windows.Forms.MessageBox]::Show( 'Cài đặt hoàn tất.', 'Trình cài đặt Mắt Bão Invoice', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) | Out-Null } catch { Add-InstallLog "Lỗi: $($_.Exception.Message)" $actionLabel.Text = 'Cài đặt thất bại. Vui lòng xem nhật ký.' [System.Windows.Forms.MessageBox]::Show( $_.Exception.Message, 'Cài đặt thất bại', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) | Out-Null } finally { $progress.Style = 'Continuous' $buttonInstall.Enabled = $true $buttonReinstallX86.Enabled = -not $isLegacyWindows $buttonRefresh.Enabled = $true $buttonClose.Enabled = $true } }) $buttonReinstallX86.Add_Click({ $confirm = [System.Windows.Forms.MessageBox]::Show( 'Chế độ này sẽ gỡ bản Mắt Bão Invoice hiện tại và cài lại bản 32 bit. Bạn muốn tiếp tục?', 'Cài lại bản 32 bit', [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Warning ) if ($confirm -ne [System.Windows.Forms.DialogResult]::Yes) { return } $buttonInstall.Enabled = $false $buttonReinstallX86.Enabled = $false $buttonRefresh.Enabled = $false $buttonClose.Enabled = $false $progress.Style = 'Marquee' $actionLabel.Text = 'Đang gỡ bản hiện tại và cài lại bản 32 bit...' try { $radioX86.Checked = $true Add-InstallLog 'Người dùng chọn cài lại bản 32 bit để khắc phục lỗi ký số.' Reinstall-MatBaoInvoice32Bit ` -InstallDotNetIfMissing $checkDotNet.Checked ` -UseSilentAppInstall $checkSilent.Checked & $refreshStatus $actionLabel.Text = 'Đã cài lại bản 32 bit.' [System.Windows.Forms.MessageBox]::Show( 'Đã gỡ bản hiện tại và cài lại Mắt Bão Invoice 32 bit.', 'Cài lại bản 32 bit hoàn tất', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) | Out-Null } catch { Add-InstallLog "Lỗi: $($_.Exception.Message)" $actionLabel.Text = 'Cài lại bản 32 bit thất bại. Vui lòng xem nhật ký.' [System.Windows.Forms.MessageBox]::Show( $_.Exception.Message, 'Cài lại bản 32 bit thất bại', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) | Out-Null } finally { $progress.Style = 'Continuous' $buttonInstall.Enabled = $true $buttonReinstallX86.Enabled = -not $isLegacyWindows $buttonRefresh.Enabled = $true $buttonClose.Enabled = $true } }) & $updateSelection & $refreshStatus Add-InstallLog 'Sẵn sàng.' $form.Add_Shown({ $buttonInstall.Focus() }) [System.Windows.Forms.Application]::Run($form) } if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) { throw 'Script này chỉ dùng cho Windows.' } if ($CheckOnly) { if (Test-IsNullOrWhiteSpace $Architecture) { Get-StatusText } else { Get-StatusText -StatusArchitecture $Architecture } exit } Ensure-Administrator if ($ReinstallX86) { Reinstall-MatBaoInvoice32Bit ` -InstallDotNetIfMissing (-not $SkipDotNet) ` -UseSilentAppInstall $true exit } if ($AutoInstall) { if (Test-IsNullOrWhiteSpace $Architecture) { $Architecture = Get-DefaultArchitecture } Install-MatBaoInvoice ` -AppArchitecture $Architecture ` -InstallDotNetIfMissing (-not $SkipDotNet) ` -UseSilentAppInstall $true exit } Show-MainForm