diff --git a/VSA-Autodeploy/README.md b/VSA-Autodeploy/README.md index 458996a..7d4c1e2 100644 --- a/VSA-Autodeploy/README.md +++ b/VSA-Autodeploy/README.md @@ -1,6 +1,6 @@ # Veeam Software Appliance ISO Automation Tool -[![PowerShell](https://img.shields.io/badge/PowerShell-5.1%2B-blue.svg)](https://docs.microsoft.com/en-us/powershell/) +[![PowerShell](https://img.shields.io/badge/PowerShell-7%2B-blue.svg)](https://docs.microsoft.com/en-us/powershell/) [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![Platform](https://img.shields.io/badge/Platform-Windows%2BWSL-lightgrey.svg)](https://docs.microsoft.com/en-us/windows/wsl/) [![Veeam](https://img.shields.io/badge/Veeam-v13.0-00B336.svg)](https://www.veeam.com/) @@ -9,14 +9,44 @@ ## Overview -This advanced PowerShell script automates the customization of Veeam Software Appliance ISO files, enabling fully automated, unattended appliance deployments with enterprise-grade, reusable configurations. It supports JSON configuration loading, out-of-place ISO modification, path-safe working in the current directory, advanced logging, and optional backup creation. Network, security, and monitoring details can be configured to fit enterprise environments. +This advanced PowerShell script automates the customization of Veeam Software Appliance ISO files, enabling fully automated, unattended appliance deployments with enterprise-grade, reusable configurations. It supports JSON configuration loading, out-of-place ISO modification, advanced logging, and optional IS backup creation. Network, security, and monitoring details can be configured to fit enterprise environments. +- Tested on build 13.0.0.4967_20250822 & 13.0.1.180_20251101 +- For Auto-Deployment PowerShell Exemple : [Powershell Folder](https://github.com/BaptisteTellier/autodeploy/tree/main/powershell) --- +## What's New (v2.6) +- Now requires PowerShell 7+ +- Add Service Provider doesn't require any external tool anymore +- Node_Exporter install use offline_repo +- debug works for all VIA +- 2.6.1 : fixed issue - hardened repo not pairing automatically after deployment +- 2.6.2 : fixed issue - official updater repo re-enablement after being disabled by offline repo (node exporter & restore conf) + +## What's New (v2.5) + +- Now works with RTM_13.0.1.180_20251101 +- Confirmed with RTM : Add service provider works with SO (no logic added w/o SO yet) +- Enhanced logics with retry & Restart TTY end of script for reliability + +## What's New (v2.4) + +- Optionnal feature : Debug ! (enable root and ssh) +- Automatique unattended configuration restore now works offline + +## What's New (v2.3) + +- Optionnal feature : Automatique unattended configuration restore ! +- Improved log inside VSA + +## What's New (v2.2) + +- Now support Veeam Infrastructure Appliance (JeOS) - Proxy / VMware Proxy / Hardened Repository + ## What's New (v2.1) - Fix network configuration not applied correctly -- CFGOnly parameter to create cfg file without iso creation or modification - useful for Packer +- CFGOnly parameter to create cfg file without iso creation or modification - useful for Packer or Cloud init - NodeExporterDNF parameter to install Node Exporter with DNF (require online) ## What's New (v2.0) @@ -25,30 +55,39 @@ This advanced PowerShell script automates the customization of Veeam Software Ap - Out-of-place ISO customization by default - Optional backup creation for in-place editing - Improved script logging and in VSA logging -- Legacy and command-line overrides still supported --- ## Features - Load configuration from JSON for reproducible deployments +- APPLIANCE TYPE SELECTION: Support for VSA, VIA, VIAVMware, and VIAHR appliances with dedicated deployment workflows - Modify ISO files (create custom copies or modify in place) - Automated GRUB and Kickstart configuration injection - DHCP and static IP support, validated in script - Regional keyboard & timezone settings - Secure password and MFA configuration for Veeam accounts -- Prometheus node_exporter optional deployment -- Service Provider (VCSP) integration for v13.0.1+ -- VBR tunning exemple such as Syslog server addition +- (optional) Prometheus node_exporter deployment +- (optional) Service Provider (VCSP) integration for v13.0.1+ +- (optional) VBR licensing import and VBR tunning exemple such as Syslog server addition +- (optional) Support for Automatique unattended configuration restore +- (optional) Debug mode (enable root and ssh) - Enterprise-level logging and error handling --- +## Disclaimer : Before you edit your ISO +Installing additional Linux packages, third-party applications, or changing OS settings (other than those that can be controlled via the Veeam Host Management Console) on the Veeam Appliances is not supported. Veeam Customer Support cannot provide technical support for appliances with unsupported modifications due to their unpredictable impact on the appliance's security, stability, and performance. + +https://www.veeam.com/kb4772 + +--- + ## Prerequisites ### System Requirements - **Operating System**: Windows 10/11 or Windows Server 2016+ -- **PowerShell**: Version 5.1 or higher +- **PowerShell**: Version 7 or higher - **WSL**: Windows Subsystem for Linux (Ubuntu/Debian recommended) - **Memory**: Minimum 4GB RAM (8GB recommended for large ISOs) - **Storage**: At least 14GB free space for ISO manipulation @@ -56,31 +95,40 @@ This advanced PowerShell script automates the customization of Veeam Software Ap ### Software Dependencies **Software dependencies:** - `xorriso` installed in WSL - ` + ``` sudo apt-get update sudo apt-get install xorriso - ` + ``` - For RHEL/CentOS/Rocky: - ` + ``` sudo yum install xorriso - ` + ``` **PowerShell configuration:** - Run with an appropriate execution policy - Confirm WSL is accessible: - ` + ``` wsl --version - ` + ``` + ### Optionnal Dependencies -**License file** -- 'license' folder at / of the folder where you run the script -- xxx.lic file inside the folder and xxx.lic for the lic parameter +**VBR tunning : License file** +- `license` folder at / of the folder where you run the script +- `xxx.lic` file inside the folder and `xxx.lic` for the `LicenseFile` parameter +- `LicenseVBRTune` set to `$true` **node_exporter** -- 'node_exporter' folder at / of the folder where you run the script -- `LICENSE + node_exporter + NOTICE` inside the folder -- where `node_exporter` is the uncompressed binary downloaded from offical repo -- Warning : “fapolicyd” disallow execution of random binary – might not work in the future. Need to add node_exporter repository and rpm file installation instead +- copy offline_repo in the same folder you execut the script +- Might not work on VIA - Hardened Repository (not tested) + +**Veeam Service Provider support** +- fill json parameters starting with VCSP + +**Configuration Restore** +- download `conf` folder from repo with inside `unattended.xml`, `veeam_addsoconfpw.sh`, and your bco rename to `conftoresto.bco` (hard coded) +- Edit `unattended.xml` with your configuration password at BACKUP_PASSWORD. **It's the password for your configuration you set in VBR console.** +- Set JSON `RestoreConfig` to true and edit with your `ConfigPasswordSo`. **It's the password you set for "configuration backup" as Security Officer.** +- download `offline_repo` folder and place it at / of the folder where you run the script --- @@ -92,40 +140,43 @@ This advanced PowerShell script automates the customization of Veeam Software Ap ``` { - "SourceISO": "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso", - "OutputISO": "", - "InPlace": false, - "CreateBackup": true, - "CleanupCFGFiles": false, - "CFGOnly": false, - "GrubTimeout": 15, - "KeyboardLayout": "fr", - "Timezone": "Europe/Paris", - "Hostname": "veeam-backup", - "UseDHCP": false, - "StaticIP": "192.168.1.166", - "Subnet": "255.255.255.0", - "Gateway": "192.168.1.1", - "DNSServers": ["192.168.1.64", "8.8.8.4", "8.8.8.8"], - "VeeamAdminPassword": "123q123Q123!123", - "VeeamAdminMfaSecretKey": "JBSWY3DPEHPK3PXP", - "VeeamAdminIsMfaEnabled": "false", - "VeeamSoPassword": "123w123W123!123", - "VeeamSoMfaSecretKey": "JBSWY3DPEHPK3PXP", - "VeeamSoIsMfaEnabled": "true", - "VeeamSoRecoveryToken": "12345678-90ab-cdef-1234-567890abcdef", - "VeeamSoIsEnabled": "true", - "NtpServer": "time.nist.gov", - "NtpRunSync": "true", - "NodeExporter": false, - "NodeExporterDNF": true, - "LicenseVBRTune": true, - "LicenseFile": "Veeam-100instances-entplus-monitoring-nfr.lic", - "SyslogServer": "172.17.53.28", - "VCSPConnection": false, - "VCSPUrl": "", - "VCSPLogin": "", - "VCSPPassword": "" + "SourceISO": "VeeamSoftwareAppliance_13.0.1.180_20251101.iso", + "OutputISO": "", + "ApplianceType": "VSA", + "InPlace": false, + "CreateBackup": true, + "CleanupCFGFiles": true, + "CFGOnly": false, + "GrubTimeout": 0, + "KeyboardLayout": "fr", + "Timezone": "Europe/Paris", + "Hostname": "veeam-backup", + "UseDHCP": false, + "StaticIP": "192.168.1.166", + "Subnet": "255.255.255.0", + "Gateway": "192.168.1.1", + "DNSServers": ["192.168.1.64", "8.8.8.4", "8.8.8.8"], + "VeeamAdminPassword": "123q123Q123!123", + "VeeamAdminMfaSecretKey": "JBSWY3DPEHPK3PXP", + "VeeamAdminIsMfaEnabled": "true", + "VeeamSoPassword": "123w123W123!123", + "VeeamSoMfaSecretKey": "JBSWY3DPEHPK3PXP", + "VeeamSoIsMfaEnabled": "true", + "VeeamSoRecoveryToken": "12345678-90ab-cdef-1234-567890abcdef", + "VeeamSoIsEnabled": "true", + "NtpServer": "time.nist.gov", + "NtpRunSync": "true", + "NodeExporter": false, + "LicenseVBRTune": false, + "LicenseFile": "Veeam-100instances-entplus-monitoring-nfr.lic", + "SyslogServer": "", + "VCSPConnection": false, + "VCSPUrl": "", + "VCSPLogin": "", + "VCSPPassword": "", + "RestoreConfig": false, + "ConfigPasswordSo": "", + "Debug": false } ``` @@ -133,41 +184,13 @@ This advanced PowerShell script automates the customization of Veeam Software Ap 3. Run: ` - .\autodeployppxity.ps1 -ConfigFile "production-config.json" + .\autodeploy.ps1 -ConfigFile "production-config.json" ` +4. See [Powershell Folder](https://github.com/BaptisteTellier/autodeploy/tree/main/powershell) for Auto-Deployment PowerShell Exemple : -### Legacy Usage (Parameters on command line to override default) - -1. Place the script, ISO in the same directory. - -2. Run: - - ``` - .\autodeployppxity.ps1 ` - -SourceISO "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso" ` - -GrubTimeout 45 ` - -KeyboardLayout "us" ` - -Timezone "America/New_York" ` - -Hostname "veeam-backup-prod01" ` - -UseDHCP:$false ` - -StaticIP "10.50.100.150" ` - -Subnet "255.255.255.0" ` - -Gateway "10.50.100.1" ` - -DNSServers @("10.50.1.10", "10.50.1.11", "8.8.8.8") ` - -VeeamAdminPassword "P@ssw0rd2024!123" ` - -NodeExporter $true ` - -LicenseVBRTune $true ` - -VCSPConnection $true - ``` - -### Change default value in the script (dirty) - -1. You can also edit the script to change all default parameters - -2. Place the script, ISO in the same directory. - -3. Run: `.\autodeployppxity.ps1` - +- Create-ISO.ps1 - Generates multiple bootable ISO images for Veeam appliances ( VSA + VIA Proxy + VIA Hardened Repository ) +- AutoProvisionning.ps1 - Add ISO to VMs, change boot order to DVD-ROM and starts Hyper-V VMs +- Install-VeeamInfra.ps1 - Configures Veeam infrastructure components (VIA Proxy + VIA Hardened Repository) --- @@ -180,14 +203,15 @@ This advanced PowerShell script automates the customization of Veeam Software Ap | ConfigFile | String | Path to JSON file | "" | No | | SourceISO | String | Source ISO filename (required) | VeeamSoftwareAppliance_13.0.0.4967_20250822.iso | Yes | | OutputISO | String | Customized ISO filename | auto (adds _customized) | No | +| ApplianceType | String | VSA, VIA, VIAVMware, and VIAHR | VSA | No | | InPlace | Bool | Modify original ISO directly | false | No | | CreateBackup | Bool | Create backup for InPlace changes | true | No | | CleanupCFGFiles| Bool | Clean temp config files | true | No | -| CFGOnly | Bool | write cfg file and don't work with iso | false | No | +| CFGOnly | Bool | write cfg file and don't touch ISO (for cloudInit/packer) | false | No | | GrubTimeout | Int | GRUB timeout (seconds) | 10 | No | | KeyboardLayout| String | Keyboard code | fr | No | | Timezone | String | System timezone | Europe/Paris | No | -| Hostname | String | Hostname for appliance | veeam-server | No | +| Hostname | String | Hostname for appliance (15char max for Microsoft Domaine) | veeam-server | No | ### Network Parameters @@ -212,31 +236,44 @@ This advanced PowerShell script automates the customization of Veeam Software Ap | VeeamSoRecoveryToken | String | GUID-format recovery token for SO account emergency access and recovery scenarios | `eb9fcbf4-2be6-e94d-4203-dded67c5a450` | | VeeamSoIsEnabled | String | Enable/disable the Security Officer account entirely ("true"/"false") | `"true"` | | NtpServer | String | Network Time Protocol server for time synchronization (FQDN or IP address) | `time.nist.gov` | -| NtpRunSync | String | Enable automatic time synchronization on boot ("true"/"false") | `"false"` | +| NtpRunSync | String | Enable automatic time synchronization on boot ("true"/"false") - if sync fails customization fails | `"true"` | ### Optional Features | Parameter | Type | Description | Default | |---------------------|---------|----------------------------------|-------------------------------------------| -| NodeExporter | Bool | Deploy Prometheus node_exporter Local folder required | false | -| NodeExporterDNF | Bool | Deploy Prometheus node_exporter Online required | false | -| LicenseVBRTune | Bool | Auto-install Veeam license | false | +| NodeExporter | Bool | Deploy Prometheus node_exporter - offline_repo folder required | false | +| LicenseVBRTune | Bool | Auto-install Veeam license (only VSA) | false | | LicenseFile | String | License filename | Veeam-100instances-entplus-monitoring-nfr.lic | -| SyslogServer | String | Syslog server IP | 172.17.53.28 | -| VCSPConnection | Bool | Connect to VCSP | false | +| SyslogServer | String | Syslog server IP | "" | +| VCSPConnection | Bool | Connect to VCSP (only VSA) | false | | VCSPUrl | String | VCSP server URL | "" | -| VCSPLogin | String | VCSP login | "" | -| VCSPPassword | String | VCSP password | "" | +| VCSPLogin | String | VCSP tenant's login | "" | +| VCSPPassword | String | VCSP tenant's password | "" | +| RestoreConfig | Bool | Enable unattended Configuration Restore | false | +| ConfigPasswordSo | String | SO Config Password | "" | +| Debug | Bool | enable root and ssh (don't use in production) | false | --- ### Security Notes -- **Password Requirements**: Both admin and SO passwords should be at least 15 characters long with uppercase, lowercase, numbers, and special characters for enterprise security -- **MFA Secret Keys**: Must be valid Base32-encoded strings (A-Z, 2-7, no padding) for compatibility with TOTP authenticators like Google Authenticator or Microsoft Authenticator -- **Recovery Tokens**: Should follow standard GUID format (8-4-4-4-12 hexadecimal digits) for account recovery scenarios -- **Security Officer Account**: The SO account provides service-level access separate from the administrative account for improved security separation -- **NTP Configuration**: Proper time synchronization is critical for Veeam operations, especially in distributed environments +**Password Requirements** +- The passwords for the veeamadmin and veeamso account must meet the following requirements: +- 15 characters minimum +- 1 upper case character +- 1 lower case character +- 1 numeric character +- 1 special character +- No more than 3 characters of the same class in a row. For example, you cannot use more than 3 lowercase or 3 numerical characters in sequence +- The passwords for the veeamadmin and veeamso accounts must be different +**NTP Configuration** +- To avoid timing issues with multifactor authentication, it is recommended to set ntp.runSync=true. +**MFA requirements** +- The multifactor authentication secret key must be specified as a 16 digit, Base32-encoded string. +- The recovery token must be specified using hexadecimal values — 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Note that you can generate an appropriate string with the New-Guid cmdlet in Microsoft PowerShell. +**Security Officer Account** +- The SO account provides service-level access separate from the administrative account for improved security separation ### Network Security - **IP Validation**: Comprehensive IPv4 address format validation using regex patterns @@ -248,16 +285,16 @@ This advanced PowerShell script automates the customization of Veeam Software Ap --- -## Optional feature +## How Optional Feature works : ### Node_Exporter -The script automatically creates systemd services for: -- **Node Exporter**: Prometheus monitoring with firewall configuration 9100 -- **Veeam Initialization**: One-shot service for post-boot configuration +The script install node_exporter from offline repo +- Prometheus monitoring with firewall configuration 9100 ### VBR Tunning - **License Installation**: Automated license deployment and activation - **Run custom script** : Exemple PS script : install lic and add Syslog Server +- If syslog parameters is empty, Add-VBRSyslogServer is not added Current Exemple in the script is : ``` @@ -271,26 +308,72 @@ $CustomVBRBlock = @( ) ``` -### VCSP Connection +### VCSP Connection (RTM_13.0.1.180_20251101 and above) - **VCSP Connection**: Veeam Service service provider integration with credential management & VSPC management agent flag enable +- (works with optional feature : VBR Tunning to install license) +- check `/var/log/veeam_init.log` for progression +- If you want to remotly enable analytics, for cloud init for exemple, check VCSP folder for bash script + ### CFG files Only -- **CFGOnly** : Useful for Packer deployment, you can set parameters to $true thus the script generate only CFG files and do not edit ISO +- **CFGOnly** : Useful for Packer/CloudInit deployment, you can set parameters to $true thus the script generate only CFG files and do not edit ISO + +### Automatique Unattended Restore +- (works with optional feature : VBR Tunning to install license) +- How unattended.xml works : https://helpcenter.veeam.com/docs/vbr/userguide/restore_vbr_linux_edit.html?ver=13 +- **Restore process can be very long** : check `/var/log/veeam_init.log` for progression + +To speedup the process, you can set `"vbr_control.runStart=false"` in the script ( hardcoded `function Get-VeeamHostConfigBlock`) +- find log - Password SO config: `/var/log/veeam_addsoconfpw.log` & Config restore: `/var/log/veeam_configrestore.log` +- What veeam_addsoconfpw.sh do : +``` +Retrieving local IP address +VSA URL: https://192.168.1.169:10443 +Generating TOTP code (oathtool) +TOTP code generated +Step 1/4: Authentication (curl) +Authentication successful +Step 2/4: login check +login verified +Step 3/4: Add password +Password added successfully +Step 4/5: Create current configuration password +Current configuration password created successfully +Step 5/5: Final verification +Final verification successful +Process completed successfully +``` +- install curl and oathtool from offline repo and then removes oathtool --- +## Known issues +- If you enable NtpRunSync and it fails, customization fails +- Using static IP doesn't set DNS properly : BUG in VSA, will be fix by Veeam. **Workaround :** DHCP or Enter Network in TUI parameter and Apply +- If it boots on the init wizard but it's already fully configured and you cannot go through. wait 2-3min. Then check `/var/log/veeam_init.log` (ctrl+alt+F2-F3 etc... to change TTY ) - something went wrong. **Workaround :** Reinstall + ## Troubleshooting +### Making ISO +- If you just installed WSL, you need to reboot - Ensure WSL is installed and available (`wsl --list --verbose`) - Install `xorriso` in WSL (`sudo apt-get install xorriso`) or update it +- If you just installed WSL, you might have permission issue, reboot Windows - Confirm ISO file is located in the same directory as the script - Use correct JSON structure with all parameters - You **cannot override** parameters in CLI if you use JSON - If you use optionnal features: check prerequisite and folder structure -- Use `$CFGOnly=$true` to verify your kickstart file contain all Configurations Blocks +- Use `$CFGOnly=true` to verify your kickstart file contain all Configurations Blocks - Check log file `ISO_Customization.log` for timestamped error messages - to browse ISO with WSL xorriso `wsl xorriso -indev "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso" -ls /` +### Booting ISO +- If your specified answers do not meet these requirements, the configuration process will fail. To troubleshoot errors, you can use the Live OS ISO to view the `/var/log/VeeamBackup/veeam_hostmanager/veeamhostmanager.log` file and the system logs files in the `/var/log/anaconda directory.` +- During installation and after boot, you can try CTRL+ALT+1 to 6 to change TTY, sometimes welcome wizard will dispears and you can check logs +- Post-install log and Veeam init are stored here : `/var/log/appliance-installation-logs/post-install.log` & `/var/log/veeam_init.log` +- If you enable NtpRunSync and it fails, customization fails +- Unattended Configuration Restore logs are stored here : wrong SO Password & TOTP : `/var/log/veeam_addsoconfpw.log` & wrong unattended config password or fail restore : `/var/log/veeam_configrestore.log` + ### Troubleshooting parameters #### Network Configuration Validation @@ -308,13 +391,21 @@ $CustomVBRBlock = @( ## Work with MFA & Recovery Token - For MFA creation, you can use this PowerShell : + ` $MFASecret = -join ((1..16) | ForEach-Object { "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[(Get-Random -Maximum 32)] }) + ` + + ` R2NV4ICF4GM274OU ` - For Recovery Token, you can use this PowerShell : + ` New-Guid + ` + + ` 16173f8b-54de-43c7-8364-da36a11ec8ab ` @@ -331,15 +422,21 @@ $CustomVBRBlock = @( - [x] Parameters to change Hostname ✅ **Completed** - [x] Function to change IP / DHCP ✅ **Completed** -- [ ] Move away from WSL and perhaps use oscdimg.exe ? -- [ ] Support for multiple ISO formats (JEoS & VSA) +- [x] Support for multiple ISO formats (JEoS & VSA) ✅ **Completed** - [x] Automated backup creation before modification ✅ **Completed** - [x] Support for JSON configuration file ✅ **Completed** +- [x] Automated Restore Configuration ✅ **Completed** +- [x] Automated Restore Configuration offline ✅ **Completed** +- [ ] Test Automated Restore Configuration offline with RTM/GA +- [x] Add offline repo support for node_exporter instead of binary ✅ **Completed** +- [ ] Remove curl after install to only keep curl-minimal (automated conf restore) + ## Support ### Documentation Resources - [Veeam Backup & Replication Documentation](https://helpcenter.veeam.com/docs/vbr/userguide/overview.html?ver=13) +- [Veeam Software Appliance Unattended Documentation](https://helpcenter.veeam.com/docs/vbr/userguide/deployment_linux_silent_deploy_configure.html?ver=13) - [PowerShell Documentation](https://docs.microsoft.com/en-us/powershell/) - [Rocky Linux Kickstart Guide](https://docs.rockylinux.org/guides/automation/kickstart/) - [Node Exporter Releases](https://github.com/prometheus/node_exporter/releases) @@ -348,8 +445,8 @@ $CustomVBRBlock = @( ## Author & Stats **Author**: Baptiste TELLIER -**Version**: 2.1 -**Creation**: September 26, 2025 +**Version**: 2.6.2 +**Creation**: November 28, 2025 ![GitHub stars](https://img.shields.io/github/stars/PleXi00/autodeploy) ![GitHub forks](https://img.shields.io/github/forks/PleXi00/autodeploy) diff --git a/VSA-Autodeploy/autodeploy.ps1 b/VSA-Autodeploy/autodeploy.ps1 new file mode 100644 index 0000000..b555e78 --- /dev/null +++ b/VSA-Autodeploy/autodeploy.ps1 @@ -0,0 +1,1962 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS +Veeam Appliance ISO Automation Tool + +.LICENSE +MIT License - see LICENSE file for details + +.AUTHOR +Baptiste TELLIER + +.COPYRIGHT +Copyright (c) 2025 Baptiste TELLIER + +.VERSION 2.6.2 + +.DESCRIPTION +This PowerShell script provides automation for customizing Veeam Appliance ISO files to enable fully automated, unattended installations. +Now supports two appliance types: VSA (Veeam Software Appliance) and VIA (Veeam Infrastructure Appliance). + +The script is designed to run in the same directory as the source ISO file and creates customized copies without complex path handling. + +Enhanced Features: +- APPLIANCE TYPE SELECTION: Support for VSA, VIA, VIAVMware, and VIAHR appliances with dedicated deployment workflows +- JSON CONFIGURATION SUPPORT: Load all parameters from JSON configuration files for easy deployment management +- OUT-OF-PLACE ISO MODIFICATION: Creates customized copies without modifying the original ISO +- PATH HANDLING: Works ONLY in the current directory to avoid WSL path issues +- Network Configuration: Supports both DHCP and static IP configurations with comprehensive validation +- Regional Settings: Configures keyboard layouts and timezone settings with proper validation +- Veeam Configuration Management: Implements Veeam auto deploy +- Component Integration: Optional deployment of node_exporter monitoring and Veeam license automation +- Service Provider Integration: Automated VCSP connection and management agent installation - v13.0.1 required +- Enterprise Logging: Comprehensive logging system with timestamped Info/Warn/Error levels + output log file in current folder + +The script utilizes WSL (Windows Subsystem for Linux) with xorriso for ISO manipulation. + +official Veeam documentation: https://helpcenter.veeam.com/docs/vbr/userguide/deployment_linux_silent_deploy_configure.html?ver=13 + +.PARAMETER ApplianceType +Specifies the type of Veeam appliance to customize. Valid values: "VSA", "VIA", "VIAVMware", "VIAHR" +- VSA: Veeam Software appliance (default behavior) +- VIA: Veeam Infrastructure Appliance (JeOS - Proxy) +- VIAVMware: Veeam Infrastructure Appliance (with iSCSI & NVMe/TCP) +- VIAHR: Veeam Hardened Repository (JeOS - Hardened Repository) +Default: "VSA" + +.PARAMETER ConfigFile +Path to JSON configuration file containing all script parameters. When specified, parameters from JSON file take precedence over default values. +Command line parameters will override JSON values. Example: "production-config.json" + +.PARAMETER SourceISO +Specifies the filename of the source Veeam Software Appliance ISO file in the current directory. +Default: "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso" + +.PARAMETER OutputISO +Specifies the filename for the customized ISO output in the current directory. +If empty, creates a file with "_customized" suffix. +Example: "veeam-prod.iso" + +.PARAMETER InPlace +Switch parameter to modify the original ISO file directly instead of creating a new one. +When $false (default), creates a new customized ISO preserving the original. +Default: $false + +.PARAMETER CreateBackup +Switch parameter to create a timestamped backup when using InPlace modification. +Only effective when InPlace is $true. Default: $true + +.PARAMETER CleanupCFGFiles +Switch parameter to keep grub.cfg and vbr-ks.cfg for debug +Default: $true + +.PARAMETER CFGOnly +Switch parameter to only create grub.cfg and vbr-ks.cfg for debug +Default: $false + +.PARAMETER GrubTimeout +Sets the GRUB bootloader timeout value in seconds. Default: 10 + +.PARAMETER KeyboardLayout +Keyboard layout code (e.g., 'us', 'fr', 'de', 'uk'). Default: "fr" + +.PARAMETER Timezone +System timezone (e.g., 'Europe/Paris', 'America/New_York'). Default: "Europe/Paris" + +.PARAMETER Hostname +Hostname for the deployed Veeam appliance. Default: "veeam-server" + +.PARAMETER UseDHCP +Switch parameter to configure network interface for DHCP. When set, static IP parameters are ignored. +Default: $false + +.PARAMETER StaticIP +IP address for static network configuration. Required when UseDHCP is $false. +Must be a valid IPv4 address format. Example: "192.168.1.100" + +.PARAMETER Subnet +Subnet mask for static network configuration. Required when UseDHCP is $false. +Must be a valid IPv4 subnet mask format. Example: "255.255.255.0" + +.PARAMETER Gateway +Gateway IP address for static network configuration. Required when UseDHCP is $false. +Must be a valid IPv4 address format. Example: "192.168.1.1" + +.PARAMETER DNSServers +Array of DNS server IP addresses for static network configuration. +Default: @("192.168.1.64", "8.8.4.4") + +##### Veeam Security Configuration ##### + +.PARAMETER VeeamAdminPassword +Password for the Veeam Backup & Replication administrator account. +Must meet enterprise security complexity requirements: minimum 15 characters with uppercase, lowercase, numbers, and special characters. +This account provides full administrative access to the Veeam console and all backup operations. +Default: "123q123Q123!123" + +.PARAMETER VeeamAdminMfaSecretKey +Base32-encoded secret key for multi-factor authentication (MFA) for the admin account. +Used for TOTP (Time-based One-Time Password) authentication with apps like Google Authenticator or Microsoft Authenticator. +Must be a valid Base32 string (A-Z, 2-7, no padding) between 16-32 characters. +Default: "JBSWY3DPEHPK3PXP" + +.PARAMETER VeeamAdminIsMfaEnabled +Enable or disable multi-factor authentication for the administrator account. +Recommended setting is "true" for enhanced security in production environments. +When enabled, users must provide both password and TOTP code for console access. +Default: "true" + +.PARAMETER VeeamSoPassword +Password for the Veeam Security Officer (SO) account. +The SO account provides service-level access separate from administrative functions for improved security separation. +Must meet the same complexity requirements as the admin password. +Default: "123w123W123!123" + +.PARAMETER VeeamSoMfaSecretKey +Base32-encoded MFA secret key for the Security Officer account. +Follows the same format requirements as the admin MFA key. +Should be different from the admin account's MFA key for security isolation. +Used for TOTP authentication when SO account access is required. +Default: "JBSWY3DPEHPK3PXP" + +.PARAMETER VeeamSoIsMfaEnabled +Enable or disable multi-factor authentication for the Security Officer account. +Recommended setting is "true" for production environments to secure service-level access. +When enabled, automated systems must provide TOTP codes for SO account operations. +Default: "true" + +.PARAMETER VeeamSoRecoveryToken +GUID-format recovery token for Security Officer account emergency access. +Used for account recovery scenarios when MFA devices are unavailable or lost. +Must follow standard GUID format: 8-4-4-4-12 hexadecimal digits (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). +Store this token securely in your organization's password management system. +Default: "eb9fcbf4-2be6-e94d-4203-dded67c5a450" + +.PARAMETER VeeamSoIsEnabled +Enable or disable the Security Officer account entirely. +When set to "true", creates and configures the SO account with specified security settings. +When set to "false", only the administrator account is configured. +Recommended setting is "true" for enterprise deployments requiring role separation. +Default: "true" + +.PARAMETER NtpServer +Network Time Protocol (NTP) server for system time synchronization. +Accepts either fully qualified domain name (FQDN) or IP address. +Proper time synchronization is critical for Veeam operations, backup scheduling, and certificate validation. +Recommended to use your organization's internal NTP servers or reliable public pools. +Examples: "pool.ntp.org", "time.windows.com", "192.168.1.10" +Default: "time.nist.gov" + +.PARAMETER NodeExporter +Boolean flag to enable node_exporter deployment. +Default: $false + +.PARAMETER LicenseVBRTune +Boolean flag to enable automatic Veeam license installation. Default: $false + +.PARAMETER SyslogServer +Syslog server IP address. +Default: "" + +.PARAMETER VCSPConnection +Boolean flag to enable VCSP connection. +Default: $false + +.PARAMETER RestoreConfig +Boolean flag to enable restoration of the configuration from backup. +Default: $false + +.PARAMETER ConfigPasswordSo +Security Officer Configuration Password for decrypting the configuration backup during restore. + +.PARAMETER Debug +Enable debug mode for SSH access during installation. When enabled: +- Keeps SSH service enabled +- Sets root password to plaintext (123q123Q123!123) +- Enables root SSH login +- Opens SSH port (22) in firewall temporarily +WARNING: Only use in test/development environments. Do not use in production. +Default: $false + +.EXAMPLE +Using JSON configuration file with VSA appliance (Recommended) +.\autodeploy.ps1 -ConfigFile "production-config.json" + +.NOTES +File Name : autodeploy.ps1 +Author : Baptiste TELLIER +Prerequisite : PowerShell 7+, WSL with xorriso installed +Version : 2.6.1 +Creation Date : 24/09/2025 +Last Modified : 26/11/2025 + +REQUIREMENTS: +- Windows Subsystem for Linux (WSL) with xorriso package installed +- Source ISO file must be in the same directory as this script +- Optional: JSON configuration file for simplified parameter management +- Optional: 'license' folder with .lic files for license automation +- Optional: 'node_exporter' folder with binaries for monitoring deployment +- Optional: 'conf' folder for unattended configuration restore + +USAGE: +- Place this script in the same directory as your ISO file +- Create a JSON configuration file with your desired settings +- Run the script with -ConfigFile and -ApplianceType parameters +- All operations happen in the current directory + +JSON CONFIGURATION: +The script supports loading all parameters from a JSON configuration file. Command line parameters will override JSON values. +See example JSON file for proper structure and supported parameters. + +OUTPUT: +- Customized ISO +- grub.cfg (optional) +- vbr-ks.cfg (optional) +- ISO_Customization.log + +#> + +#region Parameters +param ( + # Appliance Type Selection + [ValidateSet("VSA", "VIA", "VIAVMware", "VIAHR")] + [string]$ApplianceType = "VSA", + + # JSON Configuration File + [string]$ConfigFile = "", + + # Core Parameters + [string]$SourceISO = "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso", + [string]$OutputISO = "", + [switch]$InPlace = $false, + [bool]$CreateBackup = $true, + + ##DEBUG### + [bool]$CleanupCFGFiles = $true, + [bool]$CFGOnly = $false, + + ##### GRUB Configuration ##### + [int]$GrubTimeout = 10, + + ##### OS configuration ##### + [string]$KeyboardLayout = "fr", + [string]$Timezone = "Europe/Paris", + + ##### Network configuration ##### + [string]$Hostname = "veeam-server", + [switch]$UseDHCP = $false, + [string]$StaticIP = "192.168.1.166", + [string]$Subnet = "255.255.255.0", + [string]$Gateway = "192.168.1.1", + [string[]]$DNSServers = @("192.168.1.64", "8.8.4.4"), + + ##### Veeam configuration ##### + [string]$VeeamAdminPassword = "123q123Q123!123", + [string]$VeeamAdminMfaSecretKey = "JBSWY3DPEHPK3PXP", + [string]$VeeamAdminIsMfaEnabled = "true", + [string]$VeeamSoPassword = "123w123W123!123", + [string]$VeeamSoMfaSecretKey = "JBSWY3DPEHPK3PXP", + [string]$VeeamSoIsMfaEnabled = "true", + [string]$VeeamSoRecoveryToken = "eb9fcbf4-2be6-e94d-4203-dded67c5a450", + [string]$VeeamSoIsEnabled = "true", + [string]$NtpServer = "time.nist.gov", + [string]$NtpRunSync = "true", + + ##### optional features ##### + [bool]$NodeExporter = $false, + [bool]$LicenseVBRTune = $false, + [string]$LicenseFile = "Veeam-100instances-entplus-monitoring-nfr.lic", + [string]$SyslogServer = "", + [bool]$VCSPConnection = $false, + [string]$VCSPUrl = "", + [string]$VCSPLogin = "", + [string]$VCSPPassword = "", + [bool]$RestoreConfig = $false, + [string]$ConfigPasswordSo = "", + [bool]$Debug = $false + +) +#endregion + +#region Logging Functions + +function Write-Log { + param( + [string]$Message, + [ValidateSet('Info','Warn','Error')][string]$Level = 'Info' + ) + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + switch ($Level) { + 'Info' { Write-Host "[$timestamp][INFO] $Message" -ForegroundColor Cyan } + 'Warn' { Write-Warning "[$timestamp][WARN] $Message" } + 'Error' { Write-Host "[$timestamp][ERROR] $Message" -ForegroundColor Red } + } +} + +#endregion + +#region JSON Configuration Functions + +function Import-JSONConfig { + param( + [Parameter(Mandatory = $true)] + [string]$ConfigFilePath + ) + + try { + if (-not (Test-Path $ConfigFilePath)) { + throw "Configuration file not found: $ConfigFilePath" + } + + Write-Log "Loading configuration from: $ConfigFilePath" 'Info' + $jsonContent = Get-Content $ConfigFilePath -Raw -ErrorAction Stop + $config = $jsonContent | ConvertFrom-Json -ErrorAction Stop + + Write-Log "JSON configuration loaded successfully" 'Info' + return $config + } + catch { + Write-Log "Failed to load JSON configuration: $($_.Exception.Message)" 'Error' + throw "JSON configuration error: $($_.Exception.Message)" + } +} + +function Update-ParametersFromJSON { + param( + [Parameter(Mandatory = $true)] + [PSCustomObject]$Config + ) + + Write-Log "Applying JSON configuration..." 'Info' + + $parametersUpdated = 0 + + if ($Config.PSObject.Properties['ApplianceType'] -and -not $PSBoundParameters.ContainsKey('ApplianceType')) { + $script:ApplianceType = $Config.ApplianceType + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['SourceISO'] -and -not $PSBoundParameters.ContainsKey('SourceISO')) { + $script:SourceISO = $Config.SourceISO + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['OutputISO'] -and -not $PSBoundParameters.ContainsKey('OutputISO')) { + $script:OutputISO = $Config.OutputISO + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['InPlace'] -and -not $PSBoundParameters.ContainsKey('InPlace')) { + $script:InPlace = $Config.InPlace + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['CreateBackup'] -and -not $PSBoundParameters.ContainsKey('CreateBackup')) { + $script:CreateBackup = $Config.CreateBackup + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['CleanupCFGFiles'] -and -not $PSBoundParameters.ContainsKey('CleanupCFGFiles')) { + $script:CleanupCFGFiles = $Config.CleanupCFGFiles + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['CFGOnly'] -and -not $PSBoundParameters.ContainsKey('CFGOnly')) { + $script:CFGOnly = $Config.CFGOnly + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['GrubTimeout'] -and -not $PSBoundParameters.ContainsKey('GrubTimeout')) { + $script:GrubTimeout = $Config.GrubTimeout + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['KeyboardLayout'] -and -not $PSBoundParameters.ContainsKey('KeyboardLayout')) { + $script:KeyboardLayout = $Config.KeyboardLayout + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['Timezone'] -and -not $PSBoundParameters.ContainsKey('Timezone')) { + $script:Timezone = $Config.Timezone + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['Hostname'] -and -not $PSBoundParameters.ContainsKey('Hostname')) { + $script:Hostname = $Config.Hostname + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['UseDHCP'] -and -not $PSBoundParameters.ContainsKey('UseDHCP')) { + $script:UseDHCP = $Config.UseDHCP + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['StaticIP'] -and -not $PSBoundParameters.ContainsKey('StaticIP')) { + $script:StaticIP = $Config.StaticIP + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['Subnet'] -and -not $PSBoundParameters.ContainsKey('Subnet')) { + $script:Subnet = $Config.Subnet + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['Gateway'] -and -not $PSBoundParameters.ContainsKey('Gateway')) { + $script:Gateway = $Config.Gateway + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['DNSServers'] -and -not $PSBoundParameters.ContainsKey('DNSServers')) { + $script:DNSServers = $Config.DNSServers + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamAdminPassword'] -and -not $PSBoundParameters.ContainsKey('VeeamAdminPassword')) { + $script:VeeamAdminPassword = $Config.VeeamAdminPassword + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamAdminMfaSecretKey'] -and -not $PSBoundParameters.ContainsKey('VeeamAdminMfaSecretKey')) { + $script:VeeamAdminMfaSecretKey = $Config.VeeamAdminMfaSecretKey + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamAdminIsMfaEnabled'] -and -not $PSBoundParameters.ContainsKey('VeeamAdminIsMfaEnabled')) { + $script:VeeamAdminIsMfaEnabled = $Config.VeeamAdminIsMfaEnabled + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamSoPassword'] -and -not $PSBoundParameters.ContainsKey('VeeamSoPassword')) { + $script:VeeamSoPassword = $Config.VeeamSoPassword + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamSoMfaSecretKey'] -and -not $PSBoundParameters.ContainsKey('VeeamSoMfaSecretKey')) { + $script:VeeamSoMfaSecretKey = $Config.VeeamSoMfaSecretKey + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamSoIsMfaEnabled'] -and -not $PSBoundParameters.ContainsKey('VeeamSoIsMfaEnabled')) { + $script:VeeamSoIsMfaEnabled = $Config.VeeamSoIsMfaEnabled + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamSoRecoveryToken'] -and -not $PSBoundParameters.ContainsKey('VeeamSoRecoveryToken')) { + $script:VeeamSoRecoveryToken = $Config.VeeamSoRecoveryToken + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VeeamSoIsEnabled'] -and -not $PSBoundParameters.ContainsKey('VeeamSoIsEnabled')) { + $script:VeeamSoIsEnabled = $Config.VeeamSoIsEnabled + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['NtpServer'] -and -not $PSBoundParameters.ContainsKey('NtpServer')) { + $script:NtpServer = $Config.NtpServer + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['NtpRunSync'] -and -not $PSBoundParameters.ContainsKey('NtpRunSync')) { + $script:NtpRunSync = $Config.NtpRunSync + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['NodeExporter'] -and -not $PSBoundParameters.ContainsKey('NodeExporter')) { + $script:NodeExporter = $Config.NodeExporter + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['LicenseVBRTune'] -and -not $PSBoundParameters.ContainsKey('LicenseVBRTune')) { + $script:LicenseVBRTune = $Config.LicenseVBRTune + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['LicenseFile'] -and -not $PSBoundParameters.ContainsKey('LicenseFile')) { + $script:LicenseFile = $Config.LicenseFile + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['SyslogServer'] -and -not $PSBoundParameters.ContainsKey('SyslogServer')) { + $script:SyslogServer = $Config.SyslogServer + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VCSPConnection'] -and -not $PSBoundParameters.ContainsKey('VCSPConnection')) { + $script:VCSPConnection = $Config.VCSPConnection + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VCSPUrl'] -and -not $PSBoundParameters.ContainsKey('VCSPUrl')) { + $script:VCSPUrl = $Config.VCSPUrl + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VCSPLogin'] -and -not $PSBoundParameters.ContainsKey('VCSPLogin')) { + $script:VCSPLogin = $Config.VCSPLogin + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['VCSPPassword'] -and -not $PSBoundParameters.ContainsKey('VCSPPassword')) { + $script:VCSPPassword = $Config.VCSPPassword + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['RestoreConfig'] -and -not $PSBoundParameters.ContainsKey('RestoreConfig')) { + $script:RestoreConfig = $Config.RestoreConfig + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['ConfigPasswordSo'] -and -not $PSBoundParameters.ContainsKey('ConfigPasswordSo')) { + $script:ConfigPasswordSo = $Config.ConfigPasswordSo + $parametersUpdated++ + } + + if ($Config.PSObject.Properties['Debug'] -and -not $PSBoundParameters.ContainsKey('Debug')) { + $script:Debug = $Config.Debug + $parametersUpdated++ + } + + Write-Log "Applied $parametersUpdated parameters from JSON configuration" 'Info' +} + +#endregion + +#region Helper Functions + +function Test-Prerequisites { + Write-Log "Testing prerequisites..." 'Info' + + try { + $wslTest = & wsl echo "test" 2>$null + if ($wslTest -ne "test") { + throw "WSL is not available or not responding correctly" + } + Write-Log "WSL is available" 'Info' + } + catch { + Write-Log "WSL test failed: $($_.Exception.Message)" 'Error' + return $false + } + + try { + $xorrisoTest = & wsl which xorriso 2>$null + if ([string]::IsNullOrWhiteSpace($xorrisoTest)) { + throw "xorriso not found. Install with: wsl sudo apt-get install xorriso" + } + Write-Log "xorriso is available at: $xorrisoTest" 'Info' + } + catch { + Write-Log "xorriso test failed: $($_.Exception.Message)" 'Error' + Write-Log "Please install xorriso: wsl sudo apt-get update && wsl sudo apt-get install xorriso" 'Error' + return $false + } + + if (-not (Test-Path $SourceISO)) { + Write-Log "Source ISO not found in current directory: $SourceISO" 'Error' + Write-Log "Please ensure the ISO file is in the same directory as this script" 'Error' + return $false + } + + Write-Log "All prerequisites validated successfully" 'Info' + return $true +} + +function Initialize-ISOOperation { + $currentDir = Get-Location + Write-Log "Working in directory: $currentDir" 'Info' + + if ($InPlace) { + $targetISO = $SourceISO + Write-Log "In-place modification mode: will modify $SourceISO directly" 'Info' + + if ($CreateBackup) { + $sourceBaseName = [System.IO.Path]::GetFileNameWithoutExtension($SourceISO) + $sourceExtension = [System.IO.Path]::GetExtension($SourceISO) + $backupName = "$sourceBaseName`_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')$sourceExtension" + + Write-Log "Creating backup: $backupName" 'Info' + Copy-Item $SourceISO $backupName -Force + Write-Log "Backup created successfully" 'Info' + $backupPath = $backupName + } else { + $backupPath = $null + } + } + else { + if ([string]::IsNullOrWhiteSpace($OutputISO)) { + $sourceBaseName = [System.IO.Path]::GetFileNameWithoutExtension($SourceISO) + $sourceExtension = [System.IO.Path]::GetExtension($SourceISO) + $targetISO = "$sourceBaseName`_customized$sourceExtension" + } else { + $targetISO = $OutputISO + } + + Write-Log "Out-of-place modification mode: creating $targetISO" 'Info' + Copy-Item $SourceISO $targetISO -Force + Write-Log "Working copy created: $targetISO" 'Info' + $backupPath = $null + } + + return @{ + SourceISO = $SourceISO + TargetISO = $targetISO + BackupPath = $backupPath + IsInPlace = $InPlace.IsPresent + Mode = if ($CFGOnly) {"CFG ONLY"} elseif ($InPlace) { "In-Place" } else { "Out-of-Place" } + RestoreConfig = $RestoreConfig + Debug = $Debug + } +} + +function Invoke-WSLCommand { + param( + [Parameter(Mandatory = $true)] + [string]$Command, + [Parameter(Mandatory = $false)] + [string]$Description = "WSL Command" + ) + + try { + Write-Log "Executing: $Description" 'Info' + Write-Log "Command: $Command" 'Info' + + $output = & cmd /c $Command 2>&1 + $exitCode = $LASTEXITCODE + + if ($exitCode -ne 0) { + Write-Log "Command failed with exit code $exitCode" 'Warn' + if ($output) { + Write-Log "Output: $output" 'Warn' + } + } else { + Write-Log "$Description completed successfully" 'Info' + } + + return ($exitCode -eq 0) + } + catch { + Write-Log "$Description failed: $($_.Exception.Message)" 'Error' + return $false + } +} + +function Get-ModificationSummary { + param([hashtable]$ISOInfo) + + $summary = @() + $summary += "==================================================================================================" + $summary += " ISO MODIFICATION SUMMARY" + $summary += "==================================================================================================" + $summary += "Appliance Type: $ApplianceType" + $summary += "Source ISO: $($ISOInfo.SourceISO)" + $summary += "Target ISO: $($ISOInfo.TargetISO)" + $summary += "Mode: $($ISOInfo.Mode)" + + if ($ISOInfo.BackupPath) { + $summary += "Backup: $($ISOInfo.BackupPath)" + } + + $summary += "" + $summary += "CONFIGURATION:" + $summary += " GRUB Timeout: $GrubTimeout seconds" + $summary += " Keyboard: $KeyboardLayout" + $summary += " Timezone: $Timezone" + $summary += " Hostname: $Hostname" + + if ($UseDHCP) { + $summary += " Network: DHCP" + } else { + $summary += " Network: Static IP ($StaticIP/$Subnet via $Gateway)" + $summary += " DNS: $($DNSServers -join ', ')" + } + + $summary += "" + $summary += "OPTIONAL FEATURES:" + $summary += " Node Exporter: $(if ($NodeExporter) { 'Enabled' } else { 'Disabled' })" + $summary += " Debug: $(if ($ISOInfo.Debug) { 'Enabled' } else { 'Disabled' })" + if($ApplianceType -eq "VSA"){ + $summary += " License Auto-Install: $(if ($LicenseVBRTune) { 'Enabled' } else { 'Disabled' })" + $summary += " VCSP Connection: $(if ($VCSPConnection) { 'Enabled' } else { 'Disabled' })" + $summary += " Restore Config: $(if ($ISOInfo.RestoreConfig) { 'Enabled' } else { 'Disabled' })" + } + $summary += "==================================================================================================" + + return $summary -join "`n" +} + +function Update-FileContent { + param( + [string]$FilePath, + [string]$Pattern, + [string]$Replacement + ) + + try { + $content = Get-Content $FilePath + $content = $content -replace $Pattern, $Replacement + Set-Content $FilePath $content + Write-Log "Updated with $Replacement in $FilePath" 'Info' + } + catch { + Write-Log "Failed to update $FilePath`: $($_.Exception.Message)" 'Error' + throw + } +} + +function Add-ContentAfterLine { + param( + [string]$FilePath, + [string]$TargetLine, + [string[]]$NewLines + ) + + try { + $content = Get-Content $FilePath + $newContent = @() + + foreach ($line in $content) { + $newContent += $line + if ($line -like "*$TargetLine*") { + $newContent += $NewLines + Write-Log "Added content after line: $TargetLine" 'Info' + } + } + + $newContent | Set-Content -Path $FilePath + } + catch { + Write-Log "Failed to add content to $FilePath`: $($_.Exception.Message)" 'Error' + throw + } +} + +#endregion + +#region Configuration Functions + +function Set-KeyboardLayout { + param([string]$FilePath, [string]$Layout) + + Write-Log "Setting keyboard layout to $Layout" 'Info' + Update-FileContent -FilePath $FilePath -Pattern "keyboard --xlayouts='[^']*'" -Replacement "keyboard --xlayouts='$Layout'" +} + +function Set-Timezone { + param([string]$FilePath, [string]$TimezoneValue) + + Write-Log "Setting timezone to $TimezoneValue" 'Info' + Update-FileContent -FilePath $FilePath -Pattern "timezone [^\s]+ --utc" -Replacement "timezone $TimezoneValue --utc" +} + +function Set-NetworkConfiguration { + param( + [string]$FilePath, + [string]$Hostname, + [switch]$UseDHCP, + [string]$StaticIP, + [string]$Subnet, + [string]$Gateway, + [string[]]$DNSServers + ) + + Write-Log "Configuring network settings" 'Info' + + if ($UseDHCP) { + $networkLine = "network --bootproto=dhcp --nodns --hostname=$Hostname" + Write-Log "Using DHCP configuration" 'Info' + } else { + $DNSList = $DNSServers -join "," + $networkLine = "network --bootproto=static --ip=$StaticIP --netmask=$Subnet --gateway=$Gateway --nameserver=$DNSList --hostname=$Hostname" + Write-Log "Using static IP configuration: $StaticIP" 'Info' + } + + $content = Get-Content $FilePath + $found = $false + + for ($i = 0; $i -lt $content.Count; $i++) { + if ($content[$i] -match '^network\s') { + $content[$i] = $networkLine + $found = $true + break + } + } + + if (-not $found) { + for ($i = 0; $i -lt $content.Count; $i++) { + if ($content[$i] -match '^timezone\s+') { + $newContent = $content[0..$i] + $networkLine + $content[($i+1)..($content.Count-1)] + $content = $newContent + break + } + } + } + + Set-Content $FilePath $content + Write-Log "Network configuration applied" 'Info' +} + +function Set-DebugSSHModifications { + param([string]$FilePath) + + if (-not $Debug) { + return + } + + Write-Log "Applying DEBUG mode SSH modifications..." 'Warn' + + # Block 1 & 6: Remove all 'systemctl disable sshd.service' lines + $content = Get-Content $FilePath + $newContent = @() + foreach ($line in $content) { + if ($line -notlike "*systemctl disable sshd.service*") { + $newContent += $line + } + } + Set-Content $FilePath $newContent + Write-Log "Removed systemctl disable sshd.service lines" 'Info' + + # Block 2: Change root password configuration + Update-FileContent -FilePath $FilePath -Pattern "rootpw --iscrypted --lock \*" -Replacement "rootpw --allow-ssh --plaintext 123q123Q123!123" + Write-Log "Changed root password to plaintext" 'Info' + + # Block 3: Remove root user line - add bin bash shell + + Update-FileContent -FilePath $FilePath -Pattern "user --name root --shell /sbin/nologin*" -Replacement "user --name root --shell /bin/bash" + Write-Log "Removed root user nologin configuration" 'Info' + + # Block 4: Add SSH firewall rule before static packages installation + $content = Get-Content $FilePath + $newContent = @() + foreach ($line in $content) { + if ($line -like "*log 'Install static packages'*" -or $line -like '*log "Install static packages"*') { + $newContent += "log 'Temporary allow 22 port in kickstart in debug purposes'" + $newContent += "cp /usr/lib/firewalld/zones/drop.xml /etc/firewalld/zones/drop.xml" + $newContent += 'sed -i "//i \ " /etc/firewalld/zones/drop.xml' + } + $newContent += $line + } + Set-Content $FilePath $newContent + Write-Log "Added SSH firewall rule before static packages" 'Info' + + # Block 5: Add SSH root access configuration after "Configure ssh access" + + Update-FileContent -FilePath $FilePath -Pattern 'echo "AllowGroups veeam-grp-admin".*' -Replacement "`$1`n`nlog 'Temporary enable ssh root access in testing purposes'`ncat > /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf << EOF`nAllowGroups veeam-grp-admin root`nPermitRootLogin yes`nEOF`nsystemctl restart sshd" + + Write-Log "Added SSH root access configuration" 'Info' + + # Block 5: Reset password expiration for users + $content = Get-Content $FilePath + $newContent = @() + $foundTarget = $false + foreach ($line in $content) { + $newContent += $line + if (($line -like '*systemctl enable veeamhostmanager.service*') -and -not $foundTarget) { + $newContent += 'chage -d $(date +%Y-%m-%d) root' + $newContent += 'chage -d $(date +%Y-%m-%d) veeamadmin' + $newContent += 'chage -d $(date +%Y-%m-%d) veeamso' + $newContent += 'chage -d $(date +%Y-%m-%d) veeamtui' + $newContent += 'usermod -s /bin/bash root' + $foundTarget = $true + } + } + + Set-Content $FilePath $newContent + Write-Log "reset password expiration for users" 'Info' + + Write-Log "DEBUG mode SSH modifications completed" 'Warn' +} + +#endregion + +#region Dynamic Configuration Block Functions + +function Get-CustomVBRBlock { + $block = @( + "echo 'Applying license file...'", + "pwsh -Command '", + "Import-Module /opt/veeam/powershell/Veeam.Backup.PowerShell/Veeam.Backup.PowerShell.psd1", + "Install-VBRLicense -Path /etc/veeam/license/$LicenseFile" + ) + + # Ajouter la ligne syslog seulement si SyslogServer a une valeur + if ($SyslogServer) { + $block += "Set-VBRServerSyslog -SyslogServer '$SyslogServer' -SyslogPort 514 -Protocol UDP" + } + $block += "'" + $block += "echo 'License file applied successfully'" + + return $block +} + +function Get-CustomVCSPBlock3 { +$bashScript = +@" +#============================================================================== +# enable external managers installation +#============================================================================== +echo 'enabling external managers installation...' +touch /etc/veeam/allow_external_managers_installation +echo 'external managers installation enabled' +sleep 2 + +#============================================================================== +# Add to Service Provider +#============================================================================== +echo 'Adding to Service Provider with Mgmt Agent' + +ATTEMPT=1 +SUCCESS=0 +while [ `$ATTEMPT -le 3 ]; do + echo "[Attempt `$ATTEMPT/3] Running Powershell to add cloud provider" + if pwsh -Command ' + Import-Module /opt/veeam/powershell/Veeam.Backup.PowerShell/Veeam.Backup.PowerShell.psd1 + `$credentials = Get-VBRCloudProviderCredentials -Name "$VCSPLogin" + if (-not `$credentials) { + write-Host "Credentials not found, adding new credentials" + Add-VBRCloudProviderCredentials -Name "$VCSPLogin" -Password "$VCSPPassword" + `$credentials = Get-VBRCloudProviderCredentials -Name "$VCSPLogin" + } + else { + write-Host "Credentials found" + } + write-Host "adding cloud provider..." + Add-VBRCloudProvider -Address "$VCSPUrl" -Credentials `$credentials -InstallManagementAgent -Force + write-Host "Cloud provider added successfully" + '; then + echo "[SUCCESS] Powershell add provider worked on attempt number `$ATTEMPT" + SUCCESS=1 + break + else + echo "[FAILED] adding cloud provider `$ATTEMPT" + if [ `$ATTEMPT -lt 3 ]; then + echo "Waiting 15 seconds before retry..." + sleep 15 + fi + fi + ATTEMPT=`$((ATTEMPT + 1)) +done + +if [ `$SUCCESS -eq 0 ]; then + echo '[ERROR] Failed to add service provider after 3 attempts' + exit 1 +fi + +echo 'OK : Added to Service Provider successfully' +sleep 2 +echo 'disable allow_external_managers_installation flag' +rm -f /etc/veeam/allow_external_managers_installation +echo 'flag disabled successfully' +"@ + return $bashScript -replace "`r`n", "`n" +} + +function Get-CopyLicenseBlock { + return @( + "# Copy Veeam license file from ISO to OS", + "log 'starting license file copy'", + "mkdir -p /mnt/sysimage/etc/veeam/license/", + "if [ -f /mnt/install/repo/license/$LicenseFile ]; then", + " cp -f /mnt/install/repo/license/$LicenseFile /mnt/sysimage/etc/veeam/license/$LicenseFile", + " chmod 600 /mnt/sysimage/etc/veeam/license/$LicenseFile", + " chown root:root /mnt/sysimage/etc/veeam/license/$LicenseFile", + "fi", + "log 'license file copy completed'" + ) +} + +function Get-NodeExporterFirewallBlock { + return @( + "echo 'Configure firewall for node_exporter'", + "firewall-cmd --permanent --zone=drop --add-port=9100/tcp", + "firewall-cmd --reload" + "echo 'Firewall configured for node_exporter 9100/tcp'" + ) +} + +function Get-VeeamHostConfigBlock { + return @( + "log 'starting Veeam Host Manager configuration'", + "###############################################################################", + "# Automatic Host Manager configuration file", + "###############################################################################", + "cat << EOF >> /etc/veeam/vbr_init.cfg", + "veeamadmin.password=$VeeamAdminPassword", + "veeamadmin.mfaSecretKey=$VeeamAdminMfaSecretKey", + "veeamadmin.isMfaEnabled=$VeeamAdminIsMfaEnabled", + "veeamso.password=$VeeamSoPassword", + "veeamso.mfaSecretKey=$VeeamSoMfaSecretKey", + "veeamso.isMfaEnabled=$VeeamSoIsMfaEnabled", + "veeamso.recoveryToken=$VeeamSoRecoveryToken", + "veeamso.isEnabled=$VeeamSoIsEnabled", + "ntp.servers=$NtpServer", + "ntp.runSync=$NtpRunSync", + "vbr_control.runInitIso=true", + "vbr_control.runStart=true", + "EOF", + "###############################################################################", + "# Automatic Host Manager configuration TRIGGER AFTER REBOOT", + "###############################################################################", + "log 'starting /etc/veeam/veeam-init.sh'", + "cat << 'EOF' >> /etc/veeam/veeam-init.sh", + "#!/bin/bash", + "set -eE -u -o pipefail", + "# Configuration logging", + "exec > >(tee -a '/var/log/veeam_init.log') 2>&1", + "echo 'Applying initial Veeam configuration...'", + "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg", + "echo 'Disabling veeam-init service...'", + "systemctl disable veeam-init", + "echo 'OK : Service disabled'", + "echo 'removing offline repo /tmp/offline_repo if exists'", + "rm /tmp/offline_repo -rf" + "echo 'Restarting getty services...'", + "systemctl restart getty@tty1.service", + "systemctl restart getty@tty2.service", + "systemctl restart getty@tty3.service", + "systemctl restart getty@tty4.service", + "systemctl restart getty@tty5.service", + "echo 'OK : Getty services restarted'", + "echo '==========================================='", + "echo 'Veeam VSA Initialization Completed Successfully'", + "echo '==========================================='", + "echo 'All logs consolidated in: /var/log/veeam_init.log'", + "echo '==========================================='", + "EOF", + "log 'end of /etc/veeam/veeam-init.sh'", + "chmod +x /etc/veeam/veeam-init.sh", + "log 'creation of /etc/systemd/system/veeam-init.service'", + "# Create systemd service", + "cat << EOF >> /etc/systemd/system/veeam-init.service", + "[Unit]", + "Description=One-shot daemon to run /opt/veeam/hostmanager/veeamhostmanager at next boot", + "[Service]", + "Type=oneshot", + "ExecStart=/etc/veeam/veeam-init.sh", + "RemainAfterExit=no", + "[Install]", + "WantedBy=multi-user.target", + "EOF", + "systemctl enable veeam-init.service", + "log 'Veeam Host Manager configuration completed'", + "log 'end of log /var/log/appliance-installation-logs/post-install.log'" + ) +} + +function Get-NodeExporterOfflineBlock { + return @( + "# Install node_exporter from offline repo", + "log '[1/4] Enabling offline repository...'", + "cat << EOF >> /etc/yum.repos.d/local-offline.repo", + "[local-offline]", + "name=Local Offline Repository for oathtool and curl", + "enabled=1", + "gpgcheck=0", + "baseurl=file:///tmp/offline_repo", + "EOF", + "log '[2/4] Installing node_exporter from offline repo...'", + "dnf clean all --releasever 9", + "dnf --disablerepo='*' --enablerepo='local-offline' install -y node_exporter --releasever 9", + "log 'node_exporter installation completed'", + "log 'removing offline repository /etc/yum.repos.d/local-offline.repo'", + "rm -f /etc/yum.repos.d/local-offline.repo", + "dnf clean all", + "dnf config-manager --set-enabled '*'", + + "log '[3/4] Configuring /etc/sysconfig/node_exporter ...'", + 'bash -c ''echo OPTIONS="--web.listen-address=0.0.0.0:9100" > /etc/sysconfig/node_exporter''', + + "log '[4/4] Enabling and starting node_exporter...'", + "systemctl daemon-reload", + "systemctl enable node_exporter.service", + "log 'node_exporter installation completed'" + ) +} + +function Get-InstalloathtoolOfflineBlock { + return @( + "log '[1/2] Enabling offline repository...'", + "cat << EOF >> /etc/yum.repos.d/local-offline.repo", + "[local-offline]", + "name=Local Offline Repository for oathtool and curl", + "enabled=1", + "gpgcheck=0", + "baseurl=file:///tmp/offline_repo", + "EOF", + "log '[2/2] Installing oathtool and curl from RPMs...'", + "dnf clean all --releasever 9", + "dnf --disablerepo='*' --enablerepo='local-offline' install -y oathtool curl --releasever 9", + "log 'oathtool and curl installation completed'", + "log 'removing offline repository /etc/yum.repos.d/local-offline.repo'", + "rm -f /etc/yum.repos.d/local-offline.repo", + "dnf config-manager --set-enabled '*'" + ) +} + +function Get-VeeamRestoreConfigBlock { + +$commands = @( + "sleep 10s" +) + +if ($VeeamSoIsEnabled -eq $true) { + $commands += @( + "echo 'Configuring SO backup password'", + "chmod +x '/etc/veeam/veeam_addsoconfpw.sh'", + "/bin/bash /etc/veeam/veeam_addsoconfpw.sh '$ConfigPasswordSo' '$VeeamSoMfaSecretKey' '$VeeamSoPassword'", + "echo 'OK : Configuration SO password set'", + "sleep 10s" + ) +} + +$commands += @( + "echo 'Restoring configuration...'", + '# Temporarily disable pipefail for retry logic', + 'set +e', + 'set +o pipefail', + '', + "dotnet /opt/veeam/vbr/Veeam.Backup.Configuration.UnattendedRestore.dll /file:/var/lib/veeam/unattended.xml 2>&1 | tee -a /var/log/veeam_configrestore.log", + 'if [ ${PIPESTATUS[0]} -ne 0 ]; then', + ' echo "First attempt failed, retrying in 60 seconds..."', + ' sleep 60', + ' dotnet /opt/veeam/vbr/Veeam.Backup.Configuration.UnattendedRestore.dll /file:/var/lib/veeam/unattended.xml 2>&1 | tee -a /var/log/veeam_configrestore.log', + 'fi' + '', + '# Re-enable strict mode', + 'set -e', + 'set -o pipefail', + '', + 'if [ ${PIPESTATUS[0]} -eq 0 ]; then', + "echo 'OK : Configuration restored'", + "else", + "echo 'ERROR : Configuration restore failed 2/2 attempts'", + 'exit 1', + 'fi', + "echo 'Additional logs:'" + ) + +if ($VeeamSoIsEnabled -eq $true) { + $commands += @( + "echo ' - Password SO config: /var/log/veeam_addsoconfpw.log'" + ) +} +$commands += @( + "echo ' - Config restore: /var/log/veeam_configrestore.log'" +) +if ($VeeamSoIsEnabled -eq $true) { + $commands += @( + "echo 'Cleaning up oathtool and rm unattended.xml veeam_addsoconfpw.sh ...'", + "dnf -y remove oathtool", + "dnf clean all", + "rm -f /etc/veeam/veeam_addsoconfpw.sh" + ) +} + +$commands += @( + "rm -f /var/lib/veeam/unattended.xml" + ) + +return $commands +} + +function Get-RestoreFileCopyBlock { + return @( + "# Copy Restore configuration file", + "log 'starting restore configuration file copy'", + "cp -f /mnt/install/repo/conf/conftoresto.bco /mnt/sysimage/var/lib/veeam/backup/conftoresto.bco", + "chmod 600 /mnt/sysimage/var/lib/veeam/backup/conftoresto.bco", + "chown root:root /mnt/sysimage/var/lib/veeam/backup/conftoresto.bco", + "log 'restore configuration file copy completed'" + + "# Copy unattended.xml file", + "log 'starting unattended.xml file copy'", + "cp -f /mnt/install/repo/conf/unattended.xml /mnt/sysimage/var/lib/veeam/unattended.xml", + "chmod 600 /mnt/sysimage/var/lib/veeam/unattended.xml", + "chown root:root /mnt/sysimage/var/lib/veeam/unattended.xml", + "log 'unattended.xml file copy completed'" + + "# Copy veeam_addsoconfpw.sh file", + "log 'starting veeam_addsoconfpw.sh file copy'", + "cp -f /mnt/install/repo/conf/veeam_addsoconfpw.sh /mnt/sysimage/etc/veeam/veeam_addsoconfpw.sh", + "chmod 600 /mnt/sysimage/etc/veeam/veeam_addsoconfpw.sh", + "chown root:root /mnt/sysimage/etc/veeam/veeam_addsoconfpw.sh", + "log 'veeam_addsoconfpw.sh file copy completed'" + ) +} + +function Get-OfflineRepoFileCopyBlock { + return @( + "# Copy Offline Repo files", + "log 'starting offline repo copy'", + "cp -fr /mnt/install/repo/offline_repo /mnt/sysimage/tmp/offline_repo", + "log 'copy offline repo completed'" + ) +} + +#endregion + +#region VSA Region Function + +function Invoke-VSA { + Write-Log "==================================================================================================" + Write-Log " VSA WORKFLOW - VEEAM SOFTWARE APPLIANCE" + Write-Log "==================================================================================================" + + Write-Log "Config only set to $CFGOnly" 'Info' + if($CFGOnly){ + $script:CleanupCFGFiles=$false + $script:InPlace=$true + $script:CreateBackup=$false + } + + if (-not (Test-Prerequisites)) { + throw "Prerequisites check failed. Please resolve the issues above." + } + + if (-not $UseDHCP) { + if ([string]::IsNullOrWhiteSpace($StaticIP) -or + [string]::IsNullOrWhiteSpace($Subnet) -or + [string]::IsNullOrWhiteSpace($Gateway)) { + throw "Static IP configuration requires StaticIP, Subnet, and Gateway parameters" + } + } + + $isoInfo = Initialize-ISOOperation + + Write-Host "`n$(Get-ModificationSummary -ISOInfo $isoInfo)" -ForegroundColor Yellow + Write-Host "`nPress Enter to continue or Ctrl+C to abort..." -ForegroundColor Cyan + Read-Host + + Write-Log "Extracting configuration files from ISO..." 'Info' + + $extractCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract vbr-ks.cfg vbr-ks.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract /EFI/BOOT/grub.cfg grub.cfg" + ) + + foreach ($cmd in $extractCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Extract configuration files")) { + throw "Failed to extract files from ISO" + } + } + + @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { + if (-not (Test-Path $_)) { + throw "Required file not extracted: $_" + } + Write-Log "File extracted: $_" 'Info' + } + + ##### + #GRUB + ##### + + Write-Log "Configuring GRUB bootloader..." 'Info' + Update-FileContent -FilePath "grub.cfg" -Pattern '^(.*inst.ks=hd:LABEL=VeeamSA:/vbr-ks.cfg quiet.*)$' -Replacement '${1} inst.assumeyes' + $newDefault = '"Veeam Backup & Replication v13.0>Install - fresh install, wipes everything (including local backups)"' + Update-FileContent -FilePath "grub.cfg" -Pattern 'set default=.*' -Replacement "set default=$newDefault" + Update-FileContent -FilePath "grub.cfg" -Pattern 'set timeout=.*' -Replacement "set timeout=$GrubTimeout" + + ##### + #KSICKSTART + ##### + + Write-Log "Configuring Kickstart file..." 'Info' + Set-KeyboardLayout -FilePath "vbr-ks.cfg" -Layout $KeyboardLayout + Set-Timezone -FilePath "vbr-ks.cfg" -TimezoneValue $Timezone + + if ($UseDHCP) { + Set-NetworkConfiguration -FilePath "vbr-ks.cfg" -Hostname $Hostname -UseDHCP:$true + } else { + Set-NetworkConfiguration -FilePath "vbr-ks.cfg" -Hostname $Hostname -StaticIP $StaticIP -Subnet $Subnet -Gateway $Gateway -DNSServers $DNSServers + } + + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "mkdir -p /var/log/veeam/" -NewLines @("touch /etc/veeam/cockpit_auto_test_disable_init") + + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine 'find /etc/yum.repos.d/ -type f -not -name "*veeam*" -delete' -NewLines (Get-VeeamHostConfigBlock) + + ##### + #Optional Modifications + ##### + + ##### + #SSh modifications for DEBUG mode + ##### + + if ($Debug) { + Set-DebugSSHModifications -FilePath "vbr-ks.cfg" + } + + ##### + #Restore Configuration + ##### + + if ($RestoreConfig) { + Write-Log "Adding restore configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-RestoreFileCopyBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-OfflineRepoFileCopyBlock) + + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-VeeamRestoreConfigBlock) + if ($VeeamSoIsEnabled -eq $true) { + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine 'dnf install -y --nogpgcheck --disablerepo="*" /tmp/static-packages/*.rpm' -NewLines (Get-InstalloathtoolOfflineBlock) + } + if(-not $CFGOnly){ + if (Test-Path "conf") { + $confCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map conf /conf" + Invoke-WSLCommand -Command $confCmd -Description "Add conf folder to ISO" | Out-Null + } + if (Test-Path "offline_repo") { + $confCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map offline_repo /offline_repo" + Invoke-WSLCommand -Command $confCmd -Description "Add offline_repo folder to ISO" | Out-Null + } + } + } + + ##### + #VCSP Configuration + ##### + + if ($VCSPConnection) { + Write-Log "Adding VCSP configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-CustomVCSPBlock3) + } + + ##### + #VBR License Configuration + ##### + + if ($LicenseVBRTune) { + Write-Log "Adding license configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-CustomVBRBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-CopyLicenseBlock) + if(-not $CFGOnly){ + if (Test-Path "license") { + $licenseCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map license /license" + Invoke-WSLCommand -Command $licenseCmd -Description "Add license folder to ISO" | Out-Null + } + } + } + + ##### + #Node Exporter Configuration + ##### + + if ($NodeExporter) { + Write-Log "Adding node_exporter configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-OfflineRepoFileCopyBlock) + + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-NodeExporterFirewallBlock) + + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "dnf install -y --nogpgcheck --disablerepo="*" /tmp/static-packages/*.rpm" -NewLines (Get-NodeExporterOfflineBlock) + + if(-not $CFGOnly){ + if (Test-Path "offline_repo") { + $confCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map offline_repo /offline_repo" + Invoke-WSLCommand -Command $confCmd -Description "Add offline_repo folder to ISO" | Out-Null + } + } + } + + ##### + ##Normalize line endings & commit changes to ISO + ##### + + Write-Log "Normalizing line endings..." 'Info' + @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { + $content = Get-Content $_ -Raw + $content = $content.Replace("`r`n", "`n") + Set-Content $_ $content -NoNewline -Encoding utf8 + } + + if(-not $CFGOnly){ + Write-Log "Committing changes to ISO..." 'Info' + + $commitCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm vbr-ks.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map vbr-ks.cfg vbr-ks.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm /EFI/BOOT/grub.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map grub.cfg /EFI/BOOT/grub.cfg" + ) + + foreach ($cmd in $commitCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Commit changes to ISO")) { + throw "Failed to commit changes to ISO" + } + } + + Write-Log "ISO customization completed successfully!" 'Info' + } + + Write-Host "`n==================================================================================================" -ForegroundColor Green + Write-Host " SUCCESS!" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + Write-Host "Customized ISO: $($isoInfo.TargetISO)" -ForegroundColor Green + if ($isoInfo.BackupPath) { + Write-Host "Backup created: $($isoInfo.BackupPath)" -ForegroundColor Green + } + Write-Host "Mode: $($isoInfo.Mode)" -ForegroundColor Green + if ($isoInfo.RestoreConfig) { + Write-Host "Backup Configuration Restore : $($isoInfo.RestoreConfig)" -ForegroundColor DarkYellow + } + if ($isoInfo.Debug) { + Write-Host "Debug mode : $($isoInfo.Debug)" -ForegroundColor DarkYellow + } + Write-Host "==================================================================================================" -ForegroundColor Green + + if($CleanupCFGFiles){ + @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { + if (Test-Path $_) { + Remove-Item $_ -Force + } + } + } + + return $isoInfo +} + +#endregion + +#region VIA Region Function + +function Invoke-VIA { + Write-Log "==================================================================================================" + Write-Log " VIA WORKFLOW - VEEAM INFRASTRUCTURE APPLIANCE" + Write-Log "==================================================================================================" + + $CFGname = "proxy-ks.cfg" + + Write-Log "Config only set to $CFGOnly" 'Info' + if($CFGOnly){ + $script:CleanupCFGFiles=$false + $script:InPlace=$true + $script:CreateBackup=$false + } + + if (-not (Test-Prerequisites)) { + throw "Prerequisites check failed. Please resolve the issues above." + } + + if (-not $UseDHCP) { + if ([string]::IsNullOrWhiteSpace($StaticIP) -or + [string]::IsNullOrWhiteSpace($Subnet) -or + [string]::IsNullOrWhiteSpace($Gateway)) { + throw "Static IP configuration requires StaticIP, Subnet, and Gateway parameters" + } + } + + $isoInfo = Initialize-ISOOperation + + Write-Host "`n$(Get-ModificationSummary -ISOInfo $isoInfo)" -ForegroundColor Yellow + Write-Host "`nPress Enter to continue or Ctrl+C to abort..." -ForegroundColor Cyan + Read-Host + + Write-Log "Extracting configuration files from ISO..." 'Info' + + $extractCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract $CFGname $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract /EFI/BOOT/grub.cfg grub.cfg" + ) + + foreach ($cmd in $extractCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Extract configuration files")) { + throw "Failed to extract files from ISO" + } + } + + @("$CFGname", "grub.cfg") | ForEach-Object { + if (-not (Test-Path $_)) { + throw "Required file not extracted: $_" + } + Write-Log "File extracted: $_" 'Info' + } + + Write-Log "Configuring GRUB bootloader..." 'Info' + $pattern = "^(.*LABEL=VeeamJeOS:/$CFGname quiet.*)$" + Update-FileContent -FilePath "grub.cfg" -Pattern $pattern -Replacement '${1} inst.assumeyes' + $newDefault = '"Veeam Infrastructure Appliance>Install - fresh install, wipes everything (including local backups)"' + Update-FileContent -FilePath "grub.cfg" -Pattern 'set default=.*' -Replacement "set default=$newDefault" + Update-FileContent -FilePath "grub.cfg" -Pattern 'set timeout=.*' -Replacement "set timeout=$GrubTimeout" + + Write-Log "Configuring Kickstart file..." 'Info' + Set-KeyboardLayout -FilePath "$CFGname" -Layout $KeyboardLayout + Set-Timezone -FilePath "$CFGname" -TimezoneValue $Timezone + + if ($UseDHCP) { + Set-NetworkConfiguration -FilePath "$CFGname" -Hostname $Hostname -UseDHCP:$true + } else { + Set-NetworkConfiguration -FilePath "$CFGname" -Hostname $Hostname -StaticIP $StaticIP -Subnet $Subnet -Gateway $Gateway -DNSServers $DNSServers + } + + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine "mkdir -p /var/log/veeam/" -NewLines @("touch /etc/veeam/cockpit_auto_test_disable_init") + + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine 'find /etc/yum.repos.d/ -type f -not -name "*veeam*" -delete' -NewLines (Get-VeeamHostConfigBlock) + ##### + #Optional Modifications + ##### + + ##### + #SSh modifications for DEBUG mode + ##### + if ($Debug) { + Set-DebugSSHModifications -FilePath "$CFGname" + } + + ##### + #Node Exporter Configuration + ##### + + if ($NodeExporter) { + Write-Log "Adding node_exporter configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-OfflineRepoFileCopyBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-NodeExporterOfflineBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-NodeExporterFirewallBlock) + if(-not $CFGOnly){ + if (Test-Path "offline_repo") { + $confCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map offline_repo /offline_repo" + Invoke-WSLCommand -Command $confCmd -Description "Add offline_repo folder to ISO" | Out-Null + } + } + } + + Write-Log "Normalizing line endings..." 'Info' + @("$CFGname", "grub.cfg") | ForEach-Object { + $content = Get-Content $_ -Raw + $content = $content.Replace("`r`n", "`n") + Set-Content $_ $content -NoNewline + } + + if(-not $CFGOnly){ + Write-Log "Committing changes to ISO..." 'Info' + + $commitCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map $CFGname $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm /EFI/BOOT/grub.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map grub.cfg /EFI/BOOT/grub.cfg" + ) + + foreach ($cmd in $commitCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Commit changes to ISO")) { + throw "Failed to commit changes to ISO" + } + } + + Write-Log "ISO customization completed successfully!" 'Info' + } + + Write-Host "`n==================================================================================================" -ForegroundColor Green + Write-Host " SUCCESS!" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + Write-Host "Customized ISO: $($isoInfo.TargetISO)" -ForegroundColor Green + if ($isoInfo.BackupPath) { + Write-Host "Backup created: $($isoInfo.BackupPath)" -ForegroundColor Green + } + Write-Host "Appliance Type: $($ApplianceType)" -ForegroundColor Green + Write-Host "Mode: $($isoInfo.Mode)" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + + if($CleanupCFGFiles){ + @("$CFGname", "grub.cfg") | ForEach-Object { + if (Test-Path $_) { + Remove-Item $_ -Force + } + } + } + + return $isoInfo +} + +#endregion + +#region VIAVMware Region Function + +function Invoke-VIAVMware { + Write-Log "==================================================================================================" + Write-Log " VIA WORKFLOW - Veeam Infrastructure Appliance (with iSCSI & NVMe/TCP)" + Write-Log "==================================================================================================" + + $CFGname = "vmware-proxy-ks.cfg" + + Write-Log "Config only set to $CFGOnly" 'Info' + if($CFGOnly){ + $script:CleanupCFGFiles=$false + $script:InPlace=$true + $script:CreateBackup=$false + } + + if (-not (Test-Prerequisites)) { + throw "Prerequisites check failed. Please resolve the issues above." + } + + if (-not $UseDHCP) { + if ([string]::IsNullOrWhiteSpace($StaticIP) -or + [string]::IsNullOrWhiteSpace($Subnet) -or + [string]::IsNullOrWhiteSpace($Gateway)) { + throw "Static IP configuration requires StaticIP, Subnet, and Gateway parameters" + } + } + + $isoInfo = Initialize-ISOOperation + + Write-Host "`n$(Get-ModificationSummary -ISOInfo $isoInfo)" -ForegroundColor Yellow + Write-Host "`nPress Enter to continue or Ctrl+C to abort..." -ForegroundColor Cyan + Read-Host + + Write-Log "Extracting configuration files from ISO..." 'Info' + + $extractCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract $CFGname $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract /EFI/BOOT/grub.cfg grub.cfg" + ) + + foreach ($cmd in $extractCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Extract configuration files")) { + throw "Failed to extract files from ISO" + } + } + + @("$CFGname", "grub.cfg") | ForEach-Object { + if (-not (Test-Path $_)) { + throw "Required file not extracted: $_" + } + Write-Log "File extracted: $_" 'Info' + } + + Write-Log "Configuring GRUB bootloader..." 'Info' + $pattern = "^(.*LABEL=VeeamJeOS:/$CFGname quiet.*)$" + Update-FileContent -FilePath "grub.cfg" -Pattern $pattern -Replacement '${1} inst.assumeyes' + $newDefault = '"Veeam Infrastructure Appliance (with iSCSI & NVMe/TCP)>Install - fresh install, wipes everything (including local backups)"' + Update-FileContent -FilePath "grub.cfg" -Pattern 'set default=.*' -Replacement "set default=$newDefault" + Update-FileContent -FilePath "grub.cfg" -Pattern 'set timeout=.*' -Replacement "set timeout=$GrubTimeout" + + Write-Log "Configuring Kickstart file..." 'Info' + Set-KeyboardLayout -FilePath "$CFGname" -Layout $KeyboardLayout + Set-Timezone -FilePath "$CFGname" -TimezoneValue $Timezone + + if ($UseDHCP) { + Set-NetworkConfiguration -FilePath "$CFGname" -Hostname $Hostname -UseDHCP:$true + } else { + Set-NetworkConfiguration -FilePath "$CFGname" -Hostname $Hostname -StaticIP $StaticIP -Subnet $Subnet -Gateway $Gateway -DNSServers $DNSServers + } + + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine "mkdir -p /var/log/veeam/" -NewLines @("touch /etc/veeam/cockpit_auto_test_disable_init") + + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine 'find /etc/yum.repos.d/ -type f -not -name "*veeam*" -delete' -NewLines (Get-VeeamHostConfigBlock) + ##### + #Optional Modifications + ##### + + ##### + #SSh modifications for DEBUG mode + ##### + if ($Debug) { + Set-DebugSSHModifications -FilePath "$CFGname" + } + + if ($NodeExporter) { + Write-Log "Adding node_exporter configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-OfflineRepoFileCopyBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-NodeExporterOfflineBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-NodeExporterFirewallBlock) + if(-not $CFGOnly){ + if (Test-Path "offline_repo") { + $confCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map offline_repo /offline_repo" + Invoke-WSLCommand -Command $confCmd -Description "Add offline_repo folder to ISO" | Out-Null + } + } + } + + Write-Log "Normalizing line endings..." 'Info' + @("$CFGname", "grub.cfg") | ForEach-Object { + $content = Get-Content $_ -Raw + $content = $content.Replace("`r`n", "`n") + Set-Content $_ $content -NoNewline + } + + if(-not $CFGOnly){ + Write-Log "Committing changes to ISO..." 'Info' + + $commitCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map $CFGname $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm /EFI/BOOT/grub.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map grub.cfg /EFI/BOOT/grub.cfg" + ) + + foreach ($cmd in $commitCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Commit changes to ISO")) { + throw "Failed to commit changes to ISO" + } + } + + Write-Log "ISO customization completed successfully!" 'Info' + } + + Write-Host "`n==================================================================================================" -ForegroundColor Green + Write-Host " SUCCESS!" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + Write-Host "Customized ISO: $($isoInfo.TargetISO)" -ForegroundColor Green + if ($isoInfo.BackupPath) { + Write-Host "Backup created: $($isoInfo.BackupPath)" -ForegroundColor Green + } + Write-Host "Appliance Type: $($ApplianceType)" -ForegroundColor Green + Write-Host "Mode: $($isoInfo.Mode)" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + + if($CleanupCFGFiles){ + @("$CFGname", "grub.cfg") | ForEach-Object { + if (Test-Path $_) { + Remove-Item $_ -Force + } + } + } + + return $isoInfo +} + +#endregion + +#region VIAHR Region Function + +function Invoke-VIAHR { + Write-Log "==================================================================================================" + Write-Log " VIA WORKFLOW - Veeam Hardened Repository" + Write-Log "==================================================================================================" + + $CFGname = "hardened-repo-ks.cfg" + + Write-Log "Config only set to $CFGOnly" 'Info' + if($CFGOnly){ + $script:CleanupCFGFiles=$false + $script:InPlace=$true + $script:CreateBackup=$false + } + + if (-not (Test-Prerequisites)) { + throw "Prerequisites check failed. Please resolve the issues above." + } + + if (-not $UseDHCP) { + if ([string]::IsNullOrWhiteSpace($StaticIP) -or + [string]::IsNullOrWhiteSpace($Subnet) -or + [string]::IsNullOrWhiteSpace($Gateway)) { + throw "Static IP configuration requires StaticIP, Subnet, and Gateway parameters" + } + } + + $isoInfo = Initialize-ISOOperation + + Write-Host "`n$(Get-ModificationSummary -ISOInfo $isoInfo)" -ForegroundColor Yellow + Write-Host "`nPress Enter to continue or Ctrl+C to abort..." -ForegroundColor Cyan + Read-Host + + Write-Log "Extracting configuration files from ISO..." 'Info' + + $extractCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract $CFGname $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract /EFI/BOOT/grub.cfg grub.cfg" + ) + + foreach ($cmd in $extractCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Extract configuration files")) { + throw "Failed to extract files from ISO" + } + } + + @("$CFGname", "grub.cfg") | ForEach-Object { + if (-not (Test-Path $_)) { + throw "Required file not extracted: $_" + } + Write-Log "File extracted: $_" 'Info' + } + + Write-Log "Configuring GRUB bootloader..." 'Info' + $pattern = "^(.*LABEL=VeeamJeOS:/$CFGname quiet.*)$" + Update-FileContent -FilePath "grub.cfg" -Pattern $pattern -Replacement '${1} inst.assumeyes' + $newDefault = '"Veeam Hardened Repository>Install - fresh install, wipes everything (including local backups)"' + Update-FileContent -FilePath "grub.cfg" -Pattern 'set default=.*' -Replacement "set default=$newDefault" + Update-FileContent -FilePath "grub.cfg" -Pattern 'set timeout=.*' -Replacement "set timeout=$GrubTimeout" + + Write-Log "Configuring Kickstart file..." 'Info' + Set-KeyboardLayout -FilePath "$CFGname" -Layout $KeyboardLayout + Set-Timezone -FilePath "$CFGname" -TimezoneValue $Timezone + + if ($UseDHCP) { + Set-NetworkConfiguration -FilePath "$CFGname" -Hostname $Hostname -UseDHCP:$true + } else { + Set-NetworkConfiguration -FilePath "$CFGname" -Hostname $Hostname -StaticIP $StaticIP -Subnet $Subnet -Gateway $Gateway -DNSServers $DNSServers + } + + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine "mkdir -p /var/log/veeam/" -NewLines @("touch /etc/veeam/cockpit_auto_test_disable_init") + + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine 'find /etc/yum.repos.d/ -type f -not -name "*veeam*" -delete' -NewLines (Get-VeeamHostConfigBlock) + ### 2.6.1 fix - hardened repo secret token not pairing automaticly + Add-ContentAfterLine -FilePath "$CFGname" -TargetLine '/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg' -NewLines ('export VEEAM_SECRETTOKEN="000000" && /opt/veeam/deployment/veeamdeploymentsvc --start-pairing --timeout -1') + ##### + #Optional Modifications + ##### + + ##### + #SSh modifications for DEBUG mode + ##### + if ($Debug) { + Set-DebugSSHModifications -FilePath "$CFGname" + } + + if ($NodeExporter) { + Write-Log "Adding node_exporter configuration..." 'Info' + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-OfflineRepoFileCopyBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines (Get-NodeExporterOfflineBlock) + Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines (Get-NodeExporterFirewallBlock) + if(-not $CFGOnly){ + if (Test-Path "offline_repo") { + $confCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map offline_repo /offline_repo" + Invoke-WSLCommand -Command $confCmd -Description "Add offline_repo folder to ISO" | Out-Null + } + } + } + + Write-Log "Normalizing line endings..." 'Info' + @("$CFGname", "grub.cfg") | ForEach-Object { + $content = Get-Content $_ -Raw + $content = $content.Replace("`r`n", "`n") + Set-Content $_ $content -NoNewline + } + + if(-not $CFGOnly){ + Write-Log "Committing changes to ISO..." 'Info' + + $commitCommands = @( + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map $CFGname $CFGname", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm /EFI/BOOT/grub.cfg", + "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map grub.cfg /EFI/BOOT/grub.cfg" + ) + + foreach ($cmd in $commitCommands) { + if (-not (Invoke-WSLCommand -Command $cmd -Description "Commit changes to ISO")) { + throw "Failed to commit changes to ISO" + } + } + + Write-Log "ISO customization completed successfully!" 'Info' + } + + Write-Host "`n==================================================================================================" -ForegroundColor Green + Write-Host " SUCCESS!" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + Write-Host "Customized ISO: $($isoInfo.TargetISO)" -ForegroundColor Green + if ($isoInfo.BackupPath) { + Write-Host "Backup created: $($isoInfo.BackupPath)" -ForegroundColor Green + } + Write-Host "Appliance Type: $($ApplianceType)" -ForegroundColor Green + Write-Host "Mode: $($isoInfo.Mode)" -ForegroundColor Green + Write-Host "==================================================================================================" -ForegroundColor Green + + if($CleanupCFGFiles){ + @("$CFGname", "grub.cfg") | ForEach-Object { + if (Test-Path $_) { + Remove-Item $_ -Force + } + } + } + + return $isoInfo +} + +#endregion + +#region Main Script Entry Point + +try { + $logFile = "ISO_Customization_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + Start-Transcript -Path $logFile -Append + + Write-Log "==================================================================================================" + Write-Log "Veeam ISO Customization Script - Version 2.6.2" + Write-Log "==================================================================================================" + + if (-not [string]::IsNullOrWhiteSpace($ConfigFile)) { + $jsonConfig = Import-JSONConfig -ConfigFilePath $ConfigFile + Update-ParametersFromJSON -Config $jsonConfig + Write-Log "Configuration loaded from JSON file: $ConfigFile" 'Info' + } else { + Write-Log "Using default parameters (no JSON configuration file specified)" 'Info' + } + + Write-Log "Selected Appliance Type: $ApplianceType" 'Info' + + switch ($ApplianceType) { + "VSA" { + Write-Log "Invoking VSA (Veeam Software Appliance) workflow..." 'Info' + $resultISO = Invoke-VSA + } + "VIA" { + Write-Log "Invoking Veeam Infrastructure Appliance workflow..." 'Info' + $resultISO = Invoke-VIA + } + "VIAVMware" { + Write-Log "Invoking Veeam Infrastructure Appliance (with iSCSI & NVMe/TCP) workflow..." 'Info' + $resultISO = Invoke-VIAVMware + } + "VIAHR" { + Write-Log "Invoking Veeam Hardened Repository workflow..." 'Info' + $resultISO = Invoke-VIAHR + } + default { + throw "Invalid ApplianceType: $ApplianceType. Valid values are 'VSA', 'VIA', 'VIAVMware', or 'VIAHR'." + } + } + + Write-Log "Script execution completed successfully" 'Info' + +} catch { + Write-Log "Script failed: $($_.Exception.Message)" 'Error' + + Write-Host "`n==================================================================================================" -ForegroundColor Red + Write-Host " FAILURE!" -ForegroundColor Red + Write-Host "==================================================================================================" -ForegroundColor Red + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Check log file: $logFile" -ForegroundColor Red + Write-Host "==================================================================================================" -ForegroundColor Red + + if($CleanupCFGFiles){ + @("vbr-ks.cfg", "proxy-ks.cfg", "vmware-proxy-ks.cfg", "hardened-repo-ks.cfg", "grub.cfg") | ForEach-Object { + if (Test-Path $_ -ErrorAction SilentlyContinue) { + Remove-Item $_ -Force -ErrorAction SilentlyContinue + } + } + } + + if ($isoInfo -and -not $isoInfo.IsInPlace -and (Test-Path $isoInfo.TargetISO -ErrorAction SilentlyContinue)) { + Write-Log "Cleaning up failed working copy" 'Info' + Remove-Item $isoInfo.TargetISO -Force -ErrorAction SilentlyContinue + } + + exit 1 +} finally { + Stop-Transcript -ErrorAction SilentlyContinue +} + +#endregion + +# End of script \ No newline at end of file diff --git a/VSA-Autodeploy/autodeployppxity.ps1 b/VSA-Autodeploy/autodeployppxity.ps1 deleted file mode 100644 index 51b32c4..0000000 --- a/VSA-Autodeploy/autodeployppxity.ps1 +++ /dev/null @@ -1,1176 +0,0 @@ -<# -.SYNOPSIS -Veeam Appliance ISO Automation Tool - -.LICENSE -MIT License - see LICENSE file for details - -.AUTHOR -Baptiste TELLIER - -.COPYRIGHT -Copyright (c) 2025 Baptiste TELLIER - -.VERSION 2.1 - -.DESCRIPTION -This PowerShell script provides automation for customizing Veeam Software Appliance ISO files to enable fully automated, unattended installations. -The script is designed to run in the same directory as the source ISO file and creates customized copies without complex path handling. - -Enhanced Features: -- JSON CONFIGURATION SUPPORT: Load all parameters from JSON configuration files for easy deployment management -- OUT-OF-PLACE ISO MODIFICATION: Creates customized copies without modifying the original ISO -- PATH HANDLING: Works ONLY in the current directory to avoid WSL path issues -- Network Configuration: Supports both DHCP and static IP configurations with comprehensive validation -- Regional Settings: Configures keyboard layouts and timezone settings with proper validation -- Veeam Configuration Management: Implements Veeam auto deploy -- Component Integration: Optional deployment of node_exporter monitoring and Veeam license automation -- Service Provider Integration: Automated VCSP connection and management agent installation - v13.0.1 required -- Enterprise Logging: Comprehensive logging system with timestamped Info/Warn/Error levels + output log file in current folder - -The script utilizes WSL (Windows Subsystem for Linux) with xorriso for ISO manipulation. - -.PARAMETER ConfigFile -Path to JSON configuration file containing all script parameters. When specified, parameters from JSON file take precedence over default values. -Command line parameters will override JSON values. Example: "production-config.json" - -.PARAMETER SourceISO -Specifies the filename of the source Veeam Software Appliance ISO file in the current directory. -Default: "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso" - -.PARAMETER OutputISO -Specifies the filename for the customized ISO output in the current directory. -If empty, creates a file with "_customized" suffix. -Example: "veeam-prod.iso" - -.PARAMETER InPlace -Switch parameter to modify the original ISO file directly instead of creating a new one. -When $false (default), creates a new customized ISO preserving the original. -Default: $false - -.PARAMETER CreateBackup -Switch parameter to create a timestamped backup when using InPlace modification. -Only effective when InPlace is $true. Default: $true - -.PARAMETER CleanupCFGFiles -Switch parameter to keep grub.cfg and vbr-ks.cfg for debug -Default: $true - -.PARAMETER CFGOnly -Switch parameter to only create grub.cfg and vbr-ks.cfg for debug -Default: $false - -.PARAMETER GrubTimeout -Sets the GRUB bootloader timeout value in seconds. Default: 10 - -.PARAMETER KeyboardLayout -Keyboard layout code (e.g., 'us', 'fr', 'de', 'uk'). Default: "fr" - -.PARAMETER Timezone -System timezone (e.g., 'Europe/Paris', 'America/New_York'). Default: "Europe/Paris" - -.PARAMETER Hostname -Hostname for the deployed Veeam appliance. Default: "veeam-server" - -.PARAMETER UseDHCP -Switch parameter to configure network interface for DHCP. When set, static IP parameters are ignored. -Default: $false - -.PARAMETER StaticIP -IP address for static network configuration. Required when UseDHCP is $false. -Must be a valid IPv4 address format. Example: "192.168.1.100" - -.PARAMETER Subnet -Subnet mask for static network configuration. Required when UseDHCP is $false. -Must be a valid IPv4 subnet mask format. Example: "255.255.255.0" - -.PARAMETER Gateway -Gateway IP address for static network configuration. Required when UseDHCP is $false. -Must be a valid IPv4 address format. Example: "192.168.1.1" - -.PARAMETER DNSServers -Array of DNS server IP addresses for static network configuration. -Default: @("192.168.1.64", "8.8.4.4") - -##### Veeam Security Configuration ##### - -.PARAMETER VeeamAdminPassword -Password for the Veeam Backup & Replication administrator account. -Must meet enterprise security complexity requirements: minimum 15 characters with uppercase, lowercase, numbers, and special characters. -This account provides full administrative access to the Veeam console and all backup operations. -Default: "123q123Q123!123" - -.PARAMETER VeeamAdminMfaSecretKey -Base32-encoded secret key for multi-factor authentication (MFA) for the admin account. -Used for TOTP (Time-based One-Time Password) authentication with apps like Google Authenticator or Microsoft Authenticator. -Must be a valid Base32 string (A-Z, 2-7, no padding) between 16-32 characters. -Default: "JBSWY3DPEHPK3PXP" - -.PARAMETER VeeamAdminIsMfaEnabled -Enable or disable multi-factor authentication for the administrator account. -Recommended setting is "true" for enhanced security in production environments. -When enabled, users must provide both password and TOTP code for console access. -Default: "true" - -.PARAMETER VeeamSoPassword -Password for the Veeam Security Officer (SO) account. -The SO account provides service-level access separate from administrative functions for improved security separation. -Must meet the same complexity requirements as the admin password. -Default: "123w123W123!123" - -.PARAMETER VeeamSoMfaSecretKey -Base32-encoded MFA secret key for the Security Officer account. -Follows the same format requirements as the admin MFA key. -Should be different from the admin account's MFA key for security isolation. -Used for TOTP authentication when SO account access is required. -Default: "JBSWY3DPEHPK3PXP" - -.PARAMETER VeeamSoIsMfaEnabled -Enable or disable multi-factor authentication for the Security Officer account. -Recommended setting is "true" for production environments to secure service-level access. -When enabled, automated systems must provide TOTP codes for SO account operations. -Default: "true" - -.PARAMETER VeeamSoRecoveryToken -GUID-format recovery token for Security Officer account emergency access. -Used for account recovery scenarios when MFA devices are unavailable or lost. -Must follow standard GUID format: 8-4-4-4-12 hexadecimal digits (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). -Store this token securely in your organization's password management system. -Default: "eb9fcbf4-2be6-e94d-4203-dded67c5a450" - -.PARAMETER VeeamSoIsEnabled -Enable or disable the Security Officer account entirely. -When set to "true", creates and configures the SO account with specified security settings. -When set to "false", only the administrator account is configured. -Recommended setting is "true" for enterprise deployments requiring role separation. -Default: "true" - -.PARAMETER NtpServer -Network Time Protocol (NTP) server for system time synchronization. -Accepts either fully qualified domain name (FQDN) or IP address. -Proper time synchronization is critical for Veeam operations, backup scheduling, and certificate validation. -Recommended to use your organization's internal NTP servers or reliable public pools. -Examples: "pool.ntp.org", "time.windows.com", "192.168.1.10" -Default: "time.nist.gov" - -.PARAMETER NodeExporter -Boolean flag to enable node_exporter deployment. -Default: $false - -.PARAMETER NodeExporterDNF -Boolean flag to enable node_exporter deployment using DNF package manager. -Default: $false - -.PARAMETER LicenseVBRTune -Boolean flag to enable automatic Veeam license installation. Default: $false - -.PARAMETER VCSPConnection -Boolean flag to enable VCSP connection. -Default: $false - -.EXAMPLE -Using JSON configuration file (Recommended) -.\autodeployppxity.ps1 -ConfigFile "production-config.json" - -.EXAMPLE -Traditional parameter usage (legacy) -.\autodeployppxity.ps1 -LocalISO "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso" -GrubTimeout 45 -KeyboardLayout "us" -Timezone "America/New_York" -Hostname "veeam-backup-prod01" -UseDHCP:$false -StaticIP "10.50.100.150" -Subnet "255.255.255.0" -Gateway "10.50.100.1" -DNSServers @("10.50.1.10", "10.50.1.11", "8.8.8.8") -VeeamAdminPassword "P@ssw0rd2024!123" -VeeamAdminMfaSecretKey "ABCDEFGH12345678IJKLMNOP" -VeeamAdminIsMfaEnabled "true" -VeeamSoPassword "S3cur3P@ss!123" -VeeamSoMfaSecretKey "ZYXWVUTS87654321QPONMLKJ" -VeeamSoIsMfaEnabled "true" -VeeamSoRecoveryToken "12345678-90ab-cdef-1234-567890abcdef" -VeeamSoIsEnabled "true" -NtpServer "pool.ntp.org" -NtpRunSync "true" -NodeExporter $true -LicenseVBRTune $true -LicenseFile "Enterprise-Plus-License.lic" -SyslogServer "10.50.1.20" -VCSPConnection $true -VCSPUrl "https://vcsp.company.com" -VCSPLogin "serviceaccount" -VCSPPassword "VCSPServiceP@ss!" - -.EXAMPLE -Simple DHCP configuration for lab environment with all optional features disable (legacy) -.\autodeployppxity.ps1 -LocalISO "VeeamAppliance-Lab.iso" -GrubTimeout 10 -KeyboardLayout "fr" -Timezone "Europe/Paris" -Hostname "veeam-lab-test" -UseDHCP:$true -VeeamAdminPassword "LabP@ss123!123" -VeeamAdminIsMfaEnabled "false" -VeeamSoPassword "SOLabP@ss123!123" -VeeamSoIsMfaEnabled "false" -VeeamSoIsEnabled "false" -NodeExporter $false -LicenseVBRTune $false -VCSPConnection $false - -.NOTES -File Name : autodeployppxity.ps1 -Author : Baptiste TELLIER (Enhanced by AI Assistant) -Prerequisite : PowerShell 5.1+, WSL with xorriso installed -Version : 2.1 -Creation Date : 24/09/2025 - -REQUIREMENTS: -- Windows Subsystem for Linux (WSL) with xorriso package installed -- Source ISO file must be in the same directory as this script -- Optional: JSON configuration file for simplified parameter management -- Optional: 'license' folder with .lic files for license automation -- Optional: 'node_exporter' folder with binaries for monitoring deployment - -USAGE: -- Place this script in the same directory as your ISO file -- Create a JSON configuration file with your desired settings -- Run the script with -ConfigFile parameter -- All operations happen in the current directory - -JSON CONFIGURATION: -The script supports loading all parameters from a JSON configuration file. Command line parameters will override JSON values. -See example JSON file for proper structure and supported parameters. - -OUTPUT: -- Customized ISO -- grub.cfg (optional) -- vbr-ks.cfg (optional) -- ISO_Customization.log - -#> - -#region Parameters -param ( - # JSON Configuration File - [string]$ConfigFile = "", - - # Core Parameters - [string]$SourceISO = "VeeamSoftwareAppliance_13.0.0.4967_20250822.iso", - [string]$OutputISO = "", # If empty, uses SourceISO name with "_customized" suffix - [switch]$InPlace = $false, # Set to $true to modify original ISO - [bool]$CreateBackup = $true, # Create backup when using InPlace - - ##DEBUG### - [bool]$CleanupCFGFiles = $true, #$true to clean CFG file from folder - [bool]$CFGOnly = $false, #no ISO creation - only CFG files in folder. Automatic set $CleanupCFGFiles=$false, $CreateBackup=$false and $InPlace=$true - - ##### GRUB Configuration ##### - [int]$GrubTimeout = 10, - - ##### OS configuration ##### - [string]$KeyboardLayout = "fr", - [string]$Timezone = "Europe/Paris", - - ##### Network configuration ##### - [string]$Hostname = "veeam-server", - [switch]$UseDHCP = $false, - [string]$StaticIP = "192.168.1.166", - [string]$Subnet = "255.255.255.0", - [string]$Gateway = "192.168.1.1", - [string[]]$DNSServers = @("192.168.1.64", "8.8.4.4"), - - ##### Veeam configuration ##### - [string]$VeeamAdminPassword = "123q123Q123!123", - [string]$VeeamAdminMfaSecretKey = "JBSWY3DPEHPK3PXP", - [string]$VeeamAdminIsMfaEnabled = "true", - [string]$VeeamSoPassword = "123w123W123!123", - [string]$VeeamSoMfaSecretKey = "JBSWY3DPEHPK3PXP", - [string]$VeeamSoIsMfaEnabled = "true", - [string]$VeeamSoRecoveryToken = "eb9fcbf4-2be6-e94d-4203-dded67c5a450", - [string]$VeeamSoIsEnabled = "true", - [string]$NtpServer = "time.nist.gov", - [string]$NtpRunSync = "false", - - ##### optional features ##### - [bool]$NodeExporter = $false, #node exporter offline from folder - [bool]$NodeExporterDNF = $false, #node exporter online with dnf - [bool]$LicenseVBRTune = $false, - [string]$LicenseFile = "Veeam-100instances-entplus-monitoring-nfr.lic", - [string]$SyslogServer = "172.17.53.28", - [bool]$VCSPConnection = $false, - [string]$VCSPUrl = "192.168.1.202", - [string]$VCSPLogin = "v13", - [string]$VCSPPassword = "Azerty123!" -) -#endregion - -#region Logging Functions - -function Write-Log { - param( - [string]$Message, - [ValidateSet('Info','Warn','Error')][string]$Level = 'Info' - ) - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - switch ($Level) { - 'Info' { Write-Host "[$timestamp][INFO] $Message" -ForegroundColor Cyan } - 'Warn' { Write-Warning "[$timestamp][WARN] $Message" } - 'Error' { Write-Host "[$timestamp][ERROR] $Message" -ForegroundColor Red } - } -} - -#endregion - -#region JSON Configuration Functions - -function Import-JSONConfig { - <# - .SYNOPSIS - Imports configuration from JSON file and updates script parameters - #> - - param( - [Parameter(Mandatory = $true)] - [string]$ConfigFilePath - ) - - try { - if (-not (Test-Path $ConfigFilePath)) { - throw "Configuration file not found: $ConfigFilePath" - } - - Write-Log "Loading configuration from: $ConfigFilePath" 'Info' - $jsonContent = Get-Content $ConfigFilePath -Raw -ErrorAction Stop - $config = $jsonContent | ConvertFrom-Json -ErrorAction Stop - - Write-Log "JSON configuration loaded successfully" 'Info' - return $config - } - catch { - Write-Log "Failed to load JSON configuration: $($_.Exception.Message)" 'Error' - throw "JSON configuration error: $($_.Exception.Message)" - } -} - -function Update-ParametersFromJSON { - <# - .SYNOPSIS - Updates script parameters with values from JSON configuration - #> - - param( - [Parameter(Mandatory = $true)] - [PSCustomObject]$Config - ) - - Write-Log "Applying JSON configuration..." 'Info' - - # Update parameters if they exist in JSON (only if not explicitly provided via command line) - $parametersUpdated = 0 - - if ($Config.PSObject.Properties['SourceISO'] -and -not $PSBoundParameters.ContainsKey('SourceISO')) { - $script:SourceISO = $Config.SourceISO - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['OutputISO'] -and -not $PSBoundParameters.ContainsKey('OutputISO')) { - $script:OutputISO = $Config.OutputISO - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['InPlace'] -and -not $PSBoundParameters.ContainsKey('InPlace')) { - $script:InPlace = $Config.InPlace - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['CreateBackup'] -and -not $PSBoundParameters.ContainsKey('CreateBackup')) { - $script:CreateBackup = $Config.CreateBackup - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['CleanupCFGFiles'] -and -not $PSBoundParameters.ContainsKey('CleanupCFGFiles')) { - $script:CleanupCFGFiles = $Config.CleanupCFGFiles - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['CFGOnly'] -and -not $PSBoundParameters.ContainsKey('CFGOnly')) { - $script:CFGOnly = $Config.CFGOnly - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['GrubTimeout'] -and -not $PSBoundParameters.ContainsKey('GrubTimeout')) { - $script:GrubTimeout = $Config.GrubTimeout - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['KeyboardLayout'] -and -not $PSBoundParameters.ContainsKey('KeyboardLayout')) { - $script:KeyboardLayout = $Config.KeyboardLayout - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['Timezone'] -and -not $PSBoundParameters.ContainsKey('Timezone')) { - $script:Timezone = $Config.Timezone - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['Hostname'] -and -not $PSBoundParameters.ContainsKey('Hostname')) { - $script:Hostname = $Config.Hostname - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['UseDHCP'] -and -not $PSBoundParameters.ContainsKey('UseDHCP')) { - $script:UseDHCP = $Config.UseDHCP - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['StaticIP'] -and -not $PSBoundParameters.ContainsKey('StaticIP')) { - $script:StaticIP = $Config.StaticIP - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['Subnet'] -and -not $PSBoundParameters.ContainsKey('Subnet')) { - $script:Subnet = $Config.Subnet - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['Gateway'] -and -not $PSBoundParameters.ContainsKey('Gateway')) { - $script:Gateway = $Config.Gateway - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['DNSServers'] -and -not $PSBoundParameters.ContainsKey('DNSServers')) { - $script:DNSServers = $Config.DNSServers - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamAdminPassword'] -and -not $PSBoundParameters.ContainsKey('VeeamAdminPassword')) { - $script:VeeamAdminPassword = $Config.VeeamAdminPassword - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamAdminMfaSecretKey'] -and -not $PSBoundParameters.ContainsKey('VeeamAdminMfaSecretKey')) { - $script:VeeamAdminMfaSecretKey = $Config.VeeamAdminMfaSecretKey - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamAdminIsMfaEnabled'] -and -not $PSBoundParameters.ContainsKey('VeeamAdminIsMfaEnabled')) { - $script:VeeamAdminIsMfaEnabled = $Config.VeeamAdminIsMfaEnabled - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamSoPassword'] -and -not $PSBoundParameters.ContainsKey('VeeamSoPassword')) { - $script:VeeamSoPassword = $Config.VeeamSoPassword - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamSoMfaSecretKey'] -and -not $PSBoundParameters.ContainsKey('VeeamSoMfaSecretKey')) { - $script:VeeamSoMfaSecretKey = $Config.VeeamSoMfaSecretKey - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamSoIsMfaEnabled'] -and -not $PSBoundParameters.ContainsKey('VeeamSoIsMfaEnabled')) { - $script:VeeamSoIsMfaEnabled = $Config.VeeamSoIsMfaEnabled - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamSoRecoveryToken'] -and -not $PSBoundParameters.ContainsKey('VeeamSoRecoveryToken')) { - $script:VeeamSoRecoveryToken = $Config.VeeamSoRecoveryToken - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VeeamSoIsEnabled'] -and -not $PSBoundParameters.ContainsKey('VeeamSoIsEnabled')) { - $script:VeeamSoIsEnabled = $Config.VeeamSoIsEnabled - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['NtpServer'] -and -not $PSBoundParameters.ContainsKey('NtpServer')) { - $script:NtpServer = $Config.NtpServer - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['NtpRunSync'] -and -not $PSBoundParameters.ContainsKey('NtpRunSync')) { - $script:NtpRunSync = $Config.NtpRunSync - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['NodeExporter'] -and -not $PSBoundParameters.ContainsKey('NodeExporter')) { - $script:NodeExporter = $Config.NodeExporter - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['NodeExporterDNF'] -and -not $PSBoundParameters.ContainsKey('NodeExporterDNF')) { - $script:NodeExporterDNF = $Config.NodeExporterDNF - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['LicenseVBRTune'] -and -not $PSBoundParameters.ContainsKey('LicenseVBRTune')) { - $script:LicenseVBRTune = $Config.LicenseVBRTune - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['LicenseFile'] -and -not $PSBoundParameters.ContainsKey('LicenseFile')) { - $script:LicenseFile = $Config.LicenseFile - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['SyslogServer'] -and -not $PSBoundParameters.ContainsKey('SyslogServer')) { - $script:SyslogServer = $Config.SyslogServer - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VCSPConnection'] -and -not $PSBoundParameters.ContainsKey('VCSPConnection')) { - $script:VCSPConnection = $Config.VCSPConnection - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VCSPUrl'] -and -not $PSBoundParameters.ContainsKey('VCSPUrl')) { - $script:VCSPUrl = $Config.VCSPUrl - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VCSPLogin'] -and -not $PSBoundParameters.ContainsKey('VCSPLogin')) { - $script:VCSPLogin = $Config.VCSPLogin - $parametersUpdated++ - } - - if ($Config.PSObject.Properties['VCSPPassword'] -and -not $PSBoundParameters.ContainsKey('VCSPPassword')) { - $script:VCSPPassword = $Config.VCSPPassword - $parametersUpdated++ - } - - Write-Log "Applied $parametersUpdated parameters from JSON configuration" 'Info' -} - -#endregion - -#region Helper Functions - -function Test-Prerequisites { - <# - .SYNOPSIS - Tests all prerequisites for the script - #> - - Write-Log "Testing prerequisites..." 'Info' - - # Test WSL availability - try { - $wslTest = & wsl echo "test" 2>$null - if ($wslTest -ne "test") { - throw "WSL is not available or not responding correctly" - } - Write-Log "WSL is available" 'Info' - } - catch { - Write-Log "WSL test failed: $($_.Exception.Message)" 'Error' - return $false - } - - # Test xorriso availability - try { - $xorrisoTest = & wsl which xorriso 2>$null - if ([string]::IsNullOrWhiteSpace($xorrisoTest)) { - throw "xorriso not found. Install with: wsl sudo apt-get install xorriso" - } - Write-Log "xorriso is available at: $xorrisoTest" 'Info' - } - catch { - Write-Log "xorriso test failed: $($_.Exception.Message)" 'Error' - Write-Log "Please install xorriso: wsl sudo apt-get update && wsl sudo apt-get install xorriso" 'Error' - return $false - } - - # Test source ISO exists in current directory - if (-not (Test-Path $SourceISO)) { - Write-Log "Source ISO not found in current directory: $SourceISO" 'Error' - Write-Log "Please ensure the ISO file is in the same directory as this script" 'Error' - return $false - } - - Write-Log "All prerequisites validated successfully" 'Info' - return $true -} - -function Initialize-ISOOperation { - <# - .SYNOPSIS - Initializes the ISO operation by determining target file and creating backups if needed - #> - - # Get current directory - $currentDir = Get-Location - Write-Log "Working in directory: $currentDir" 'Info' - - # Determine target ISO filename - if ($InPlace) { - $targetISO = $SourceISO - Write-Log "In-place modification mode: will modify $SourceISO directly" 'Info' - - # Create backup if requested - if ($CreateBackup) { - $sourceBaseName = [System.IO.Path]::GetFileNameWithoutExtension($SourceISO) - $sourceExtension = [System.IO.Path]::GetExtension($SourceISO) - $backupName = "$sourceBaseName`_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')$sourceExtension" - - Write-Log "Creating backup: $backupName" 'Info' - Copy-Item $SourceISO $backupName -Force - Write-Log "Backup created successfully" 'Info' - $backupPath = $backupName - } else { - $backupPath = $null - } - } - else { - # Out-of-place modification - if ([string]::IsNullOrWhiteSpace($OutputISO)) { - $sourceBaseName = [System.IO.Path]::GetFileNameWithoutExtension($SourceISO) - $sourceExtension = [System.IO.Path]::GetExtension($SourceISO) - $targetISO = "$sourceBaseName`_customized$sourceExtension" - } else { - $targetISO = $OutputISO - } - - Write-Log "Out-of-place modification mode: creating $targetISO" 'Info' - Copy-Item $SourceISO $targetISO -Force - Write-Log "Working copy created: $targetISO" 'Info' - $backupPath = $null - } - - return @{ - SourceISO = $SourceISO - TargetISO = $targetISO - BackupPath = $backupPath - IsInPlace = $InPlace.IsPresent - Mode = if ($CFGOnly) {"CFG ONLY"} elseif ($InPlace) { "In-Place" } else { "Out-of-Place" } - } -} - -function Invoke-WSLCommand { - <# - .SYNOPSIS - Executes WSL commands with proper error handling - #> - - param( - [Parameter(Mandatory = $true)] - [string]$Command, - [Parameter(Mandatory = $false)] - [string]$Description = "WSL Command" - ) - - try { - Write-Log "Executing: $Description" 'Info' - Write-Log "Command: $Command" 'Info' - - $output = & cmd /c $Command 2>&1 - $exitCode = $LASTEXITCODE - - if ($exitCode -ne 0) { - Write-Log "Command failed with exit code $exitCode" 'Warn' - if ($output) { - Write-Log "Output: $output" 'Warn' - } - } else { - Write-Log "$Description completed successfully" 'Info' - } - - return ($exitCode -eq 0) - } - catch { - Write-Log "$Description failed: $($_.Exception.Message)" 'Error' - return $false - } -} - -function Get-ModificationSummary { - <# - .SYNOPSIS - Generates a summary of planned modifications - #> - - param([hashtable]$ISOInfo) - - $summary = @() - $summary += "==================================================================================================" - $summary += " ISO MODIFICATION SUMMARY" - $summary += "==================================================================================================" - $summary += "Source ISO: $($ISOInfo.SourceISO)" - $summary += "Target ISO: $($ISOInfo.TargetISO)" - $summary += "Mode: $($ISOInfo.Mode)" - - if ($ISOInfo.BackupPath) { - $summary += "Backup: $($ISOInfo.BackupPath)" - } - - $summary += "" - $summary += "CONFIGURATION:" - $summary += " GRUB Timeout: $GrubTimeout seconds" - $summary += " Keyboard: $KeyboardLayout" - $summary += " Timezone: $Timezone" - $summary += " Hostname: $Hostname" - - if ($UseDHCP) { - $summary += " Network: DHCP" - } else { - $summary += " Network: Static IP ($StaticIP/$Subnet via $Gateway)" - $summary += " DNS: $($DNSServers -join ', ')" - } - - $summary += "" - $summary += "OPTIONAL FEATURES:" - $summary += " Node Exporter Local: $(if ($NodeExporter) { 'Enabled' } else { 'Disabled' })" - $summary += " Node Exporter Online: $(if ($NodeExporterDNF) { 'Enabled' } else { 'Disabled' })" - $summary += " License Auto-Install: $(if ($LicenseVBRTune) { 'Enabled' } else { 'Disabled' })" - $summary += " VCSP Connection: $(if ($VCSPConnection) { 'Enabled' } else { 'Disabled' })" - $summary += "==================================================================================================" - - return $summary -join "`n" -} - -function Update-FileContent { - <# - .SYNOPSIS - Updates file content with search and replace - #> - - param( - [string]$FilePath, - [string]$Pattern, - [string]$Replacement - ) - - try { - $content = Get-Content $FilePath - $content = $content -replace $Pattern, $Replacement - Set-Content $FilePath $content - Write-Log "Updated with $Replacement in $FilePath" 'Info' - } - catch { - Write-Log "Failed to update $FilePath`: $($_.Exception.Message)" 'Error' - throw - } -} - -function Add-ContentAfterLine { - <# - .SYNOPSIS - Adds content after a specific line in a file - #> - - param( - [string]$FilePath, - [string]$TargetLine, - [string[]]$NewLines - ) - - try { - $content = Get-Content $FilePath - $newContent = @() - - foreach ($line in $content) { - $newContent += $line - if ($line -like "*$TargetLine*") { - $newContent += $NewLines - Write-Log "Added content after line: $TargetLine" 'Info' - } - } - - Set-Content $FilePath $newContent - } - catch { - Write-Log "Failed to add content to $FilePath`: $($_.Exception.Message)" 'Error' - throw - } -} - -#endregion - -#region Configuration Functions - -function Set-KeyboardLayout { - param([string]$FilePath, [string]$Layout) - - Write-Log "Setting keyboard layout to $Layout" 'Info' - Update-FileContent -FilePath $FilePath -Pattern "keyboard --xlayouts='[^']*'" -Replacement "keyboard --xlayouts='$Layout'" -} - -function Set-Timezone { - param([string]$FilePath, [string]$TimezoneValue) - - Write-Log "Setting timezone to $TimezoneValue" 'Info' - Update-FileContent -FilePath $FilePath -Pattern "timezone [^\\s]+ --utc" -Replacement "timezone $TimezoneValue --utc" -} - -function Set-NetworkConfiguration { - param( - [string]$FilePath, - [string]$Hostname, - [switch]$UseDHCP, - [string]$StaticIP, - [string]$Subnet, - [string]$Gateway, - [string[]]$DNSServers - ) - - Write-Log "Configuring network settings" 'Info' - - if ($UseDHCP) { - $networkLine = "network --bootproto=dhcp --nodns --hostname=$Hostname" - Write-Log "Using DHCP configuration" 'Info' - } else { - $DNSList = $DNSServers -join "," - $networkLine = "network --bootproto=static --ip=$StaticIP --netmask=$Subnet --gateway=$Gateway --nameserver=$DNSList --hostname=$Hostname" - Write-Log "Using static IP configuration: $StaticIP" 'Info' - } - - # Replace existing network line or add new one - - $content = Get-Content $FilePath - $found = $false - - for ($i = 0; $i -lt $content.Count; $i++) { - if ($content[$i] -match '^network\s') { - $content[$i] = $networkLine - $found = $true - $found - break - } - } - - if (-not $found) { - # Add after timezone line - for ($i = 0; $i -lt $content.Count; $i++) { - if ($content[$i] -match '^timezone\\s+') { - $newContent = $content[0..$i] + $networkLine + $content[($i+1)..($content.Count-1)] - $content = $newContent - break - } - } - } - - Set-Content $FilePath $content - Write-Log "Network configuration applied" 'Info' -} - -#endregion - -#region Configuration Blocks - -$CustomVBRBlock = @( - "# Custom VBR config", - "pwsh -Command '", - "Import-Module /opt/veeam/powershell/Veeam.Backup.PowerShell/Veeam.Backup.PowerShell.psd1", - "Install-VBRLicense -Path /etc/veeam/license/$LicenseFile", - "Add-VBRSyslogServer -ServerHost '$SyslogServer' -Port 514 -Protocol Udp", - "'" -) - -$CustomVCSPBlock = @( - "# Connect to Service Provider with Mgmt Agent", - "pwsh -Command '", - "Import-Module /opt/veeam/powershell/Veeam.Backup.PowerShell/Veeam.Backup.PowerShell.psd1", - "Add-VBRCloudProviderCredentials -Name '$VCSPLogin' -Password '$VCSPPassword'", - "`$credentials = Get-VBRCloudProviderCredentials -Name '$VCSPLogin'", - "Add-VBRCloudProvider -Address '$VCSPConnection' -Credentials `$credentials -InstallManagementAgent", - "'" -) - -$CopyLicenseBlock = @( - "# Copy Veeam license file from ISO to OS", - "log 'starting license file copy'", - "mkdir -p /mnt/sysimage/etc/veeam/license/", - "if [ -f /mnt/install/repo/license/$LicenseFile ]; then", - " cp -f /mnt/install/repo/license/$LicenseFile /mnt/sysimage/etc/veeam/license/$LicenseFile", - " chmod 600 /mnt/sysimage/etc/veeam/license/$LicenseFile", - " chown root:root /mnt/sysimage/etc/veeam/license/$LicenseFile", - "fi", - "log 'license file copy completed'" -) - -$CopyNodeExporterBlock = @( - "# Copy node_exporter files to OS", - "log 'starting node_exporter files copy'", - "mkdir -p /mnt/sysimage/etc/node_exporter", - "if [ -d /mnt/install/repo/node_exporter ]; then", - " cp -r /mnt/install/repo/node_exporter /mnt/sysimage/etc/", - "fi", - "log 'node_exporter files copy completed'" -) - -$NodeExporterSetupBlock = @( - "# Setup node_exporter service", - "log 'starting node_exporter installation'", - "groupadd -f node_exporter", - "useradd -g node_exporter --no-create-home --shell /bin/false node_exporter", - "chown node_exporter:node_exporter /etc/node_exporter", - "cat << EOF >> /etc/systemd/system/node_exporter.service", - "[Unit]", - "Description=Node Exporter", - "Documentation=https://prometheus.io/docs/guides/node-exporter/", - "Wants=network-online.target", - "After=network-online.target", - "", - "[Service]", - "User=node_exporter", - "Group=node_exporter", - "Type=simple", - "Restart=on-failure", - "ExecStart=/etc/node_exporter/node_exporter --web.listen-address=:9100", - "", - "[Install]", - "WantedBy=multi-user.target", - "EOF", - "chmod 664 /etc/systemd/system/node_exporter.service", - "systemctl daemon-reload", - "systemctl enable node_exporter.service", - "log 'node_exporter installation completed'" -) - -$NodeExporterFirewallBlock = @( - "# Configure firewall for node_exporter", - "firewall-cmd --permanent --zone=drop --add-port=9100/tcp", - "firewall-cmd --reload" -) - -$VeeamHostConfigBlock = @( - "log 'starting Veeam Host Manager configuration'", - "###############################################################################", - "# Automatic Host Manager configuration file", - "###############################################################################", - "cat << EOF >> /etc/veeam/vbr_init.cfg", - "veeamadmin.password=$VeeamAdminPassword", - "veeamadmin.mfaSecretKey=$VeeamAdminMfaSecretKey", - "veeamadmin.isMfaEnabled=$VeeamAdminIsMfaEnabled", - "veeamso.password=$VeeamSoPassword", - "veeamso.mfaSecretKey=$VeeamSoMfaSecretKey", - "veeamso.isMfaEnabled=$VeeamSoIsMfaEnabled", - "veeamso.recoveryToken=$VeeamSoRecoveryToken", - "veeamso.isEnabled=$VeeamSoIsEnabled", - "ntp.servers=$NtpServer", - "ntp.runSync=$NtpRunSync", - "vbr_control.runInitIso=true", - "vbr_control.runStart=true", - "EOF", - "###############################################################################", - "# Automatic Host Manager configuration TRIGGER AFTER REBOOT", - "###############################################################################", - "cat << EOF >> /etc/veeam/veeam-init.sh", - "#!/bin/bash", - "set -eE -u -o pipefail", - "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg", - "systemctl disable veeam-init", - "EOF", - "chmod +x /etc/veeam/veeam-init.sh", - "# Create systemd service", - "cat << EOF >> /etc/systemd/system/veeam-init.service", - "[Unit]", - "Description=One-shot daemon to run /opt/veeam/hostmanager/veeamhostmanager at next boot", - "[Service]", - "Type=oneshot", - "ExecStart=/etc/veeam/veeam-init.sh", - "RemainAfterExit=no", - "[Install]", - "WantedBy=multi-user.target", - "EOF", - "systemctl enable veeam-init.service", - "log 'Veeam Host Manager configuration completed'" -) - -$NodeExporterDNFBlock = @( - "log '[1/4] Enabling Rocky Linux repos and EPEL...'", - "rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm", - "dnf clean all && dnf -y makecache", - "dnf -y install dnf-plugins-core || true", - "dnf -y config-manager --set-enabled crb || true", - "dnf -y install epel-release", - "dnf -y makecache", - - "log '[2/4] Installing node_exporter...'", - "dnf -y install node_exporter", - - "log '[3/4] Configuring /etc/sysconfig/node_exporter ...'", - 'bash -c ''echo OPTIONS="--web.listen-address=0.0.0.0:9100" > /etc/sysconfig/node_exporter''', - - "log '[4/4] Enabling and starting node_exporter...'", - "systemctl daemon-reload", - "systemctl enable node_exporter.service", - "log 'node_exporter installation completed'" -) - -#endregion - -#region Main Script - -try { - # Initialize logging - $logFile = "ISO_Customization_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" - Start-Transcript -Path $logFile -Append - - Write-Log "==================================================================================================" - Write-Log "Veeam ISO Customization Script - Version 2.1" - Write-Log "==================================================================================================" - - # Load JSON configuration if provided - if (-not [string]::IsNullOrWhiteSpace($ConfigFile)) { - $jsonConfig = Import-JSONConfig -ConfigFilePath $ConfigFile - Update-ParametersFromJSON -Config $jsonConfig - Write-Log "Configuration loaded from JSON file: $ConfigFile" 'Info' - } else { - Write-Log "Using default parameters (no JSON configuration file specified)" 'Info' - } - #if CFG only keep file and don't do iso backup because it won't be edit - Write-Log "Config only set to $CFGOnly" - if($CFGOnly){ - $CleanupCFGFiles=$false - $InPlace=$true - $CreateBackup=$false - } - # Test prerequisites - if (-not (Test-Prerequisites)) { - throw "Prerequisites check failed. Please resolve the issues above." - } - - # Validate network parameters - if (-not $UseDHCP) { - if ([string]::IsNullOrWhiteSpace($StaticIP) -or - [string]::IsNullOrWhiteSpace($Subnet) -or - [string]::IsNullOrWhiteSpace($Gateway)) { - throw "Static IP configuration requires StaticIP, Subnet, and Gateway parameters" - } - } - - # Initialize ISO operation - $isoInfo = Initialize-ISOOperation - - # Show summary and get confirmation - Write-Host "`n$(Get-ModificationSummary -ISOInfo $isoInfo)" -ForegroundColor Yellow - Write-Host "`nPress Enter to continue or Ctrl+C to abort..." -ForegroundColor Cyan - Read-Host - - # Extract files from target ISO - Write-Log "Extracting configuration files from ISO..." 'Info' - - $extractCommands = @( - "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract vbr-ks.cfg vbr-ks.cfg", - "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -osirrox on -extract /EFI/BOOT/grub.cfg grub.cfg" - ) - - foreach ($cmd in $extractCommands) { - if (-not (Invoke-WSLCommand -Command $cmd -Description "Extract configuration files")) { - throw "Failed to extract files from ISO" - } - } - - # Verify extracted files exist - @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { - if (-not (Test-Path $_)) { - throw "Required file not extracted: $_" - } - Write-Log "File extracted: $_" 'Info' - } - - # Configure GRUB - Write-Log "Configuring GRUB bootloader..." 'Info' - Update-FileContent -FilePath "grub.cfg" -Pattern '^(.*LABEL=Rocky-9-2-x86_64:/vbr-ks.cfg quiet.*)$' -Replacement '${1} inst.assumeyes' - $newDefault = '"Veeam Backup & Replication v13.0>Install - fresh install, wipes everything (including local backups)"' - Update-FileContent -FilePath "grub.cfg" -Pattern 'set default=.*' -Replacement "set default=$newDefault" - Update-FileContent -FilePath "grub.cfg" -Pattern 'set timeout=.*' -Replacement "set timeout=$GrubTimeout" - - # Configure Kickstart - Write-Log "Configuring Kickstart file..." 'Info' - Set-KeyboardLayout -FilePath "vbr-ks.cfg" -Layout $KeyboardLayout - Set-Timezone -FilePath "vbr-ks.cfg" -TimezoneValue $Timezone - - if ($UseDHCP) { - Set-NetworkConfiguration -FilePath "vbr-ks.cfg" -Hostname $Hostname -UseDHCP:$true - } else { - Set-NetworkConfiguration -FilePath "vbr-ks.cfg" -Hostname $Hostname -StaticIP $StaticIP -Subnet $Subnet -Gateway $Gateway -DNSServers $DNSServers - } - - # Disable init wizard - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "mkdir -p /var/log/veeam/" -NewLines @("touch /etc/veeam/cockpit_auto_test_disable_init") - - # Add Veeam host configuration - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine 'find /etc/yum.repos.d/ -type f -not -name "*veeam*" -delete' -NewLines $VeeamHostConfigBlock - - # Add optional features - if ($LicenseVBRTune) { - Write-Log "Adding license configuration..." 'Info' - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines $CustomVBRBlock - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines $CopyLicenseBlock - - # Add license folder to ISO - if (Test-Path "license") { - $licenseCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map license /license" - Invoke-WSLCommand -Command $licenseCmd -Description "Add license folder to ISO" | Out-Null - } - } - - if ($VCSPConnection) { - Write-Log "Adding VCSP configuration..." 'Info' - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines $CustomVCSPBlock - } - - if ($NodeExporter) { - Write-Log "Adding node_exporter configuration..." 'Info' - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine 'dnf install -y --nogpgcheck --disablerepo="*" /tmp/static-packages/*.rpm' -NewLines $NodeExporterSetupBlock - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/" -NewLines $CopyNodeExporterBlock - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines $NodeExporterFirewallBlock - - # Add node_exporter folder to ISO - if (Test-Path "node_exporter") { - $nodeCmd = "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map node_exporter /node_exporter" - Invoke-WSLCommand -Command $nodeCmd -Description "Add node_exporter folder to ISO" | Out-Null - } - } - - if ($NodeExporterDNF){ - Write-Log "Adding node_exporter with DNF configuration..." 'Info' - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine 'dnf install -y --nogpgcheck --disablerepo="*" /tmp/static-packages/*.rpm' -NewLines $NodeExporterDNFBlock - Add-ContentAfterLine -FilePath "vbr-ks.cfg" -TargetLine "/opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg" -NewLines $NodeExporterFirewallBlock - } - - - # Normalize line endings - Write-Log "Normalizing line endings..." 'Info' - @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { - $content = Get-Content $_ -Raw - $content = $content.Replace("`r`n", "`n") - Set-Content $_ $content -NoNewline - } - - if(-not $CFGOnly){ - # Commit changes to ISO - Write-Log "Committing changes to ISO..." 'Info' - - $commitCommands = @( - "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm vbr-ks.cfg", - "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map vbr-ks.cfg vbr-ks.cfg", - "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -rm /EFI/BOOT/grub.cfg", - "wsl xorriso -boot_image any keep -dev `"$($isoInfo.TargetISO)`" -map grub.cfg /EFI/BOOT/grub.cfg" - ) - - foreach ($cmd in $commitCommands) { - if (-not (Invoke-WSLCommand -Command $cmd -Description "Commit changes to ISO")) { - throw "Failed to commit changes to ISO" - } - } - - # Success - Write-Log "ISO customization completed successfully!" 'Info' - } - Write-Host "`n==================================================================================================" -ForegroundColor Green - Write-Host " SUCCESS!" -ForegroundColor Green - Write-Host "==================================================================================================" -ForegroundColor Green - Write-Host "Customized ISO: $($isoInfo.TargetISO)" -ForegroundColor Green - if ($isoInfo.BackupPath) { - Write-Host "Backup created: $($isoInfo.BackupPath)" -ForegroundColor Green - } - Write-Host "Mode: $($isoInfo.Mode)" -ForegroundColor Green - Write-Host "Log file: $logFile" -ForegroundColor Green - Write-Host "==================================================================================================" -ForegroundColor Green - - # Cleanup temporary files - if($CleanupCFGFiles){ - @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { - if (Test-Path $_) { - Remove-Item $_ -Force - } - } - } - -} catch { - Write-Log "Script failed: $($_.Exception.Message)" 'Error' - - Write-Host "`n==================================================================================================" -ForegroundColor Red - Write-Host " FAILURE!" -ForegroundColor Red - Write-Host "==================================================================================================" -ForegroundColor Red - Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red - Write-Host "Check log file: $logFile" -ForegroundColor Red - Write-Host "==================================================================================================" -ForegroundColor Red - - # Cleanup temporary files - if($CleanupCFGFiles){ - @("vbr-ks.cfg", "grub.cfg") | ForEach-Object { - if (Test-Path $_ -ErrorAction SilentlyContinue) { - Remove-Item $_ -Force -ErrorAction SilentlyContinue - } - } - } - - # Cleanup failed working copy (but not if in-place) - if ($isoInfo -and -not $isoInfo.IsInPlace -and (Test-Path $isoInfo.TargetISO -ErrorAction SilentlyContinue)) { - Write-Log "Cleaning up failed working copy" 'Info' - Remove-Item $isoInfo.TargetISO -Force -ErrorAction SilentlyContinue - } - - exit 1 -} finally { - Stop-Transcript -ErrorAction SilentlyContinue -} - -#endregion -# End of script \ No newline at end of file diff --git a/VSA-Autodeploy/conf/conftoresto.bco b/VSA-Autodeploy/conf/conftoresto.bco new file mode 100644 index 0000000..cc12ade Binary files /dev/null and b/VSA-Autodeploy/conf/conftoresto.bco differ diff --git a/VSA-Autodeploy/conf/unattended.xml b/VSA-Autodeploy/conf/unattended.xml new file mode 100644 index 0000000..a00838c --- /dev/null +++ b/VSA-Autodeploy/conf/unattended.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/VSA-Autodeploy/conf/veeam_addsoconfpw.sh b/VSA-Autodeploy/conf/veeam_addsoconfpw.sh new file mode 100644 index 0000000..81730bd --- /dev/null +++ b/VSA-Autodeploy/conf/veeam_addsoconfpw.sh @@ -0,0 +1,236 @@ +#!/bin/bash + +#============================================================================== +# Veeam VSA Automation Script - Configuration Password +# Auto-destruction after execution +#============================================================================== + +# Configuration +VSA_USER="veeamso" +VSA_PASSWORD="$3" +TOTP_SECRET="$2" +CONFIG_PASSWORD="$1" +VSA_PORT="10443" + +# Temporary files +COOKIE_JAR="/tmp/veeam_session_$$_$(date +%s)" +LOG_FILE="/var/log/veeam_addsoconfpw.log" +SCRIPT_PATH="$0" + +#============================================================================== +# Secure logging function +#============================================================================== +log() { + local level="$1" + shift + local message="$*" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[${timestamp}] [${level}] ${message}" >> "$LOG_FILE" + case "$level" in + INFO) + echo -e "[INFO]${NC} ${message}" + ;; + WARN) + echo -e "[WARN]${NC} ${message}" + ;; + ERROR) + echo -e "[ERROR]${NC} ${message}" + ;; + *) + echo "[${level}] ${message}" + ;; + esac +} + +#============================================================================== +# Secure cleanup function +#============================================================================== +cleanup() { + log "INFO" "Cleaning up temporary files" + if [ -f "$COOKIE_JAR" ]; then + shred -u -n 3 "$COOKIE_JAR" 2>/dev/null || rm -f "$COOKIE_JAR" + log "INFO" "Cookie jar deleted" + fi + + unset VSA_PASSWORD TOTP_SECRET TOTP_CODE CSRF_TOKEN CONFIG_PASSWORD + + log "INFO" "Script self-destruction in 2 seconds" + sleep 2 + + if command -v shred &> /dev/null; then + shred -u -n 3 "$SCRIPT_PATH" 2>/dev/null + log "INFO" "Script deleted with shred" + else + rm -f "$SCRIPT_PATH" + log "WARN" "Script deleted without shred" + fi +} + +trap cleanup EXIT + +#============================================================================== +# Preliminary checks +#============================================================================== +log "INFO" "Starting Veeam VSA automation" + +if [ -z "$CONFIG_PASSWORD" ]; then + log "ERROR" "Usage: $0 " + exit 1 +fi + +if ! command -v oathtool &> /dev/null; then + log "ERROR" "oathtool not found - Installation: dnf install oathtool" + exit 1 +fi + +if ! command -v curl &> /dev/null; then + log "ERROR" "curl not found" + exit 1 +fi + +#============================================================================== +# Retrieve local IP address +#============================================================================== +log "INFO" "Retrieving local IP address" +VSA_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + VSA_IP=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K[^ ]+') +fi + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + VSA_IP=$(ifconfig 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | head -n1) +fi + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + log "ERROR" "Unable to retrieve local IP address" + exit 1 +fi + +VSA_URL="https://${VSA_IP}:${VSA_PORT}" +log "INFO" "VSA URL: ${VSA_URL}" + +#============================================================================== +# Generate TOTP code +#============================================================================== +log "INFO" "Generating TOTP code" +TOTP_CODE=$(oathtool --totp -b "$TOTP_SECRET" 2>/dev/null) + +if [ -z "$TOTP_CODE" ]; then + log "ERROR" "TOTP generation failed" + exit 1 +fi + +log "INFO" "TOTP code generated" +TIMESTAMP=$(date +%s) + +#============================================================================== +# Step 1: Authentication +#============================================================================== +log "INFO" "Step 1/4: Authentication" +RESPONSE=$(curl -k -s -i -c "$COOKIE_JAR" -b "$COOKIE_JAR" -X POST "${VSA_URL}/api/auth/login" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "x-otp-token: ${TOTP_CODE}" \ + -H "otp-client-unixtime: ${TIMESTAMP}" \ + -H "Accept: */*" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"user\":\"${VSA_USER}\",\"password\":\"${VSA_PASSWORD}\"}" 2>&1) + +CSRF_TOKEN=$(echo "$RESPONSE" | grep -i "X-CSRF-TOKEN:" | awk '{print $2}' | tr -d '\r') + +if [ -z "$CSRF_TOKEN" ]; then + log "ERROR" "Authentication failed" + exit 1 +fi + +log "INFO" "Authentication successful" +sleep 1 + +#============================================================================== +# Step 2: Log in check +#============================================================================== +log "INFO" "Step 2/4: Configuration check" +STATUS=$(curl -k -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" -w "%{http_code}" -o /dev/null \ + -X GET "${VSA_URL}/api/v1/bco/imported?" \ + -H "Accept: application/json" \ + -H "x-csrf-token: ${CSRF_TOKEN}" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36") + +if [ "$STATUS" != "200" ]; then + log "WARN" "Check: HTTP ${STATUS}" +else + log "INFO" "login verified" +fi + +#============================================================================== +# Step 3: Add password +#============================================================================== +log "INFO" "Step 3/4: Add password" +RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/v1/bco/imported?" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Accept: application/json" \ + -H "x-csrf-token: ${CSRF_TOKEN}" \ + -H "Origin: ${VSA_URL}" \ + -H "Referer: ${VSA_URL}/configuration" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"hint\":\"\",\"passphrase\":\"${CONFIG_PASSWORD}\"}") + +HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + +if [ "$HTTP_CODE" = "200" ]; then + log "INFO" "Password added successfully" +else + log "ERROR" "Failed to add password (HTTP ${HTTP_CODE})" + exit 1 +fi + +#============================================================================== +# Step 4: Create current configuration password +#============================================================================== +log "INFO" "Step 4/5: Create current configuration password" +RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/v1/bco/current?" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Accept: application/json" \ + -H "x-csrf-token: ${CSRF_TOKEN}" \ + -H "Origin: ${VSA_URL}" \ + -H "Referer: ${VSA_URL}/configuration" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"hint\":\"\",\"passphrase\":\"${CONFIG_PASSWORD}\"}") + +HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +BODY=$(echo "$RESPONSE" | sed 's/HTTP_CODE:.*//') + +if [ "$HTTP_CODE" = "200" ]; then + log "INFO" "Current configuration password created successfully" +else + log "ERROR" "Failed to create current configuration password (HTTP ${HTTP_CODE})" + exit 1 +fi + +#============================================================================== +# Step 5: Final verification +#============================================================================== +log "INFO" "Step 5/5: Final verification" +FINAL_STATUS=$(curl -k -s -b "$COOKIE_JAR" -w "%{http_code}" -o /dev/null \ + -X GET "${VSA_URL}/api/v1/bco/imported?" \ + -H "Accept: application/json" \ + -H "x-csrf-token: ${CSRF_TOKEN}" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36") + +if [ "$FINAL_STATUS" = "200" ]; then + log "INFO" "Final verification successful" +else + log "WARN" "Final verification: HTTP ${FINAL_STATUS}" +fi + +log "INFO" "Process completed successfully" +log "INFO" "Cleanup in progress" + +exit 0 + + diff --git a/VSA-Autodeploy/log/PS ISO_Customization.log b/VSA-Autodeploy/log/PS ISO_Customization.log new file mode 100644 index 0000000..91ccad8 --- /dev/null +++ b/VSA-Autodeploy/log/PS ISO_Customization.log @@ -0,0 +1,128 @@ +********************** +Windows PowerShell transcript start +Start time: 20251026224717 +Username: +RunAs User: +Configuration Name: +Machine: +Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe K:\autodeploy vsa\test\autodeployconf.ps1 +Process ID: 5168 +PSVersion: 5.1.22621.6060 +PSEdition: Desktop +PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.22621.6060 +BuildVersion: 10.0.22621.6060 +CLRVersion: 4.0.30319.42000 +WSManStackVersion: 3.0 +PSRemotingProtocolVersion: 2.3 +SerializationVersion: 1.1.0.1 +********************** +Transcript started, output file is ISO_Customization_20251026_224717.log +[2025-10-26 22:47:17][INFO] ================================================================================================== +[2025-10-26 22:47:17][INFO] Veeam ISO Customization Script - Version 2.3 +[2025-10-26 22:47:17][INFO] ================================================================================================== +[2025-10-26 22:47:17][INFO] Loading configuration from: restore-config.json +[2025-10-26 22:47:17][INFO] JSON configuration loaded successfully +[2025-10-26 22:47:17][INFO] Applying JSON configuration... +[2025-10-26 22:47:17][INFO] Applied 37 parameters from JSON configuration +[2025-10-26 22:47:17][INFO] Configuration loaded from JSON file: restore-config.json +[2025-10-26 22:47:17][INFO] Selected Appliance Type: VSA +[2025-10-26 22:47:17][INFO] Invoking VSA (Veeam Software Appliance) workflow... +[2025-10-26 22:47:17][INFO] ================================================================================================== +[2025-10-26 22:47:17][INFO] VSA WORKFLOW - VEEAM SOFTWARE APPLIANCE +[2025-10-26 22:47:17][INFO] ================================================================================================== +[2025-10-26 22:47:17][INFO] Config only set to False +[2025-10-26 22:47:17][INFO] Testing prerequisites... +[2025-10-26 22:47:17][INFO] WSL is available +[2025-10-26 22:47:18][INFO] xorriso is available at: /usr/bin/xorriso +[2025-10-26 22:47:18][INFO] All prerequisites validated successfully +[2025-10-26 22:47:18][INFO] Working in directory: K:\autodeploy vsa\test +[2025-10-26 22:47:18][INFO] Out-of-place modification mode: creating VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso +[2025-10-26 22:47:45][INFO] Working copy created: VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso + +================================================================================================== + ISO MODIFICATION SUMMARY +================================================================================================== +Appliance Type: VSA +Source ISO: VeeamSoftwareAppliance_13.0.0.4967_20250822.iso +Target ISO: VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso +Mode: Out-of-Place + +CONFIGURATION: + GRUB Timeout: 0 seconds + Keyboard: fr + Timezone: Europe/Paris + Hostname: VSA + Network: DHCP + +OPTIONAL FEATURES: + Node Exporter Local: Disabled + Node Exporter Online: Disabled + License Auto-Install: Enabled + VCSP Connection: Disabled + Restore Config: Enabled +================================================================================================== + +Press Enter to continue or Ctrl+C to abort... +[2025-10-26 22:50:26][INFO] Extracting configuration files from ISO... +[2025-10-26 22:50:26][INFO] Executing: Extract configuration files +[2025-10-26 22:50:26][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -osirrox on -extract vbr-ks.cfg vbr-ks.cfg +[2025-10-26 22:50:26][INFO] Extract configuration files completed successfully +[2025-10-26 22:50:26][INFO] Executing: Extract configuration files +[2025-10-26 22:50:26][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -osirrox on -extract /EFI/BOOT/grub.cfg grub.cfg +[2025-10-26 22:50:26][INFO] Extract configuration files completed successfully +[2025-10-26 22:50:26][INFO] File extracted: vbr-ks.cfg +[2025-10-26 22:50:26][INFO] File extracted: grub.cfg +[2025-10-26 22:50:26][INFO] Configuring GRUB bootloader... +[2025-10-26 22:50:26][INFO] Updated with ${1} inst.assumeyes in grub.cfg +[2025-10-26 22:50:26][INFO] Updated with set default="Veeam Backup & Replication v13.0>Install - fresh install, wipes everything (including local backups)" in grub.cfg +[2025-10-26 22:50:26][INFO] Updated with set timeout=0 in grub.cfg +[2025-10-26 22:50:26][INFO] Configuring Kickstart file... +[2025-10-26 22:50:26][INFO] Setting keyboard layout to fr +[2025-10-26 22:50:26][INFO] Updated with keyboard --xlayouts='fr' in vbr-ks.cfg +[2025-10-26 22:50:26][INFO] Setting timezone to Europe/Paris +[2025-10-26 22:50:26][INFO] Updated with timezone Europe/Paris --utc in vbr-ks.cfg +[2025-10-26 22:50:26][INFO] Configuring network settings +[2025-10-26 22:50:26][INFO] Using DHCP configuration +[2025-10-26 22:50:26][INFO] Network configuration applied +[2025-10-26 22:50:26][INFO] Added content after line: mkdir -p /var/log/veeam/ +[2025-10-26 22:50:26][INFO] Added content after line: find /etc/yum.repos.d/ -type f -not -name "*veeam*" -delete +[2025-10-26 22:50:26][INFO] Adding restore configuration... +[2025-10-26 22:50:26][INFO] Added content after line: /usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/ +[2025-10-26 22:50:26][INFO] Added content after line: /opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg +[2025-10-26 22:50:26][INFO] Added content after line: dnf install -y --nogpgcheck --disablerepo="*" /tmp/static-packages/*.rpm +[2025-10-26 22:50:26][INFO] Executing: Add conf folder to ISO +[2025-10-26 22:50:26][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -map conf /conf +[2025-10-26 22:50:26][INFO] Add conf folder to ISO completed successfully +[2025-10-26 22:50:26][INFO] Adding license configuration... +[2025-10-26 22:50:26][INFO] Added content after line: /opt/veeam/hostmanager/veeamhostmanager --apply_init_config /etc/veeam/vbr_init.cfg +[2025-10-26 22:50:26][INFO] Added content after line: /usr/bin/cp -rv /tmp/*.* /mnt/sysimage/var/log/appliance-installation-logs/ +[2025-10-26 22:50:26][INFO] Executing: Add license folder to ISO +[2025-10-26 22:50:26][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -map license /license +[2025-10-26 22:50:26][INFO] Add license folder to ISO completed successfully +[2025-10-26 22:50:26][INFO] Normalizing line endings... +[2025-10-26 22:50:26][INFO] Committing changes to ISO... +[2025-10-26 22:50:26][INFO] Executing: Commit changes to ISO +[2025-10-26 22:50:26][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -rm vbr-ks.cfg +[2025-10-26 22:50:26][INFO] Commit changes to ISO completed successfully +[2025-10-26 22:50:26][INFO] Executing: Commit changes to ISO +[2025-10-26 22:50:26][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -map vbr-ks.cfg vbr-ks.cfg +[2025-10-26 22:50:27][INFO] Commit changes to ISO completed successfully +[2025-10-26 22:50:27][INFO] Executing: Commit changes to ISO +[2025-10-26 22:50:27][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -rm /EFI/BOOT/grub.cfg +[2025-10-26 22:50:27][INFO] Commit changes to ISO completed successfully +[2025-10-26 22:50:27][INFO] Executing: Commit changes to ISO +[2025-10-26 22:50:27][INFO] Command: wsl xorriso -boot_image any keep -dev "VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso" -map grub.cfg /EFI/BOOT/grub.cfg +[2025-10-26 22:50:27][INFO] Commit changes to ISO completed successfully +[2025-10-26 22:50:27][INFO] ISO customization completed successfully! + +================================================================================================== + SUCCESS! +================================================================================================== +Customized ISO: VeeamSoftwareAppliance_13.0.0.4967_20250822_customized.iso +Mode: Out-of-Place +================================================================================================== +[2025-10-26 22:50:27][INFO] Script execution completed successfully +********************** +Windows PowerShell transcript end +End time: 20251026225027 +********************** diff --git a/VSA-Autodeploy/log/VSA Restore Conf veeam_init.log b/VSA-Autodeploy/log/VSA Restore Conf veeam_init.log new file mode 100644 index 0000000..430b39b --- /dev/null +++ b/VSA-Autodeploy/log/VSA Restore Conf veeam_init.log @@ -0,0 +1,245 @@ +Applying initial Veeam configuration... +Create db connection +Configure network manager +Prepare firewalld +Accept licenses + AcceptThirdPartyLicense + AcceptVeeamLicense +Modifying chrony config +Sync chrony time +Init hardcoded users + Setup veeam admin + Setup veeam so +[TBD] Configuring VDC connector +1/0 +[TBD] Configuring VDC connector +1/0 +Finish state integration +1/12 Marking as configured. +6/12 Initializing ISO +8/12 Restarting service veeam-updater +10/12 VBR ctl operation: Start +Start veeamhostmanager +Success!!! +Applying license file... +WARNING: The names of some imported commands from the module 'Veeam.Backup.PowerShell' include unapproved verbs that might make them less discoverable. To find the commands with unapproved verbs, run the Import-Module command again with the Verbose parameter. For a list of approved verbs, type Get-Verb. +License +file +applied +successfully +Configuring SO backup password +[INFO] Starting Veeam VSA automation +[INFO] Retrieving local IP address +[INFO] VSA URL: https://192.168.1.169:10443 +[INFO] Generating TOTP code +[INFO] TOTP code generated +[INFO] Step 1/4: Authentication +[INFO] Authentication successful +[INFO] Step 2/4: Configuration check +[INFO] Configuration verified +[INFO] Step 3/4: Add password +[INFO] Password added successfully +[INFO] Step 4/4: Final verification +[INFO] Final verification successful +[INFO] Step 5/5: Create current configuration password +[INFO] Current configuration password created successfully +[INFO] Process completed successfully +[INFO] Cleanup in progress +[INFO] Cleaning up temporary files +[INFO] Cookie jar deleted +[INFO] Script self-destruction in 2 seconds +[INFO] Script deleted with shred +OK : Configuration SO password set +Restoring configuration... +Veeam.Backup.Configuration.UnattendedRestore Version 13.0.0.4967 +Copyright (C) 2006-2025 Veeam Software Group GmbH + +Starting configuration restore in unattended mode at 10/28/2025 6:47:46 PM + +Loading unattended config /var/lib/veeam/unattended.xml... +Loading backup repositories list... +Processing configuration backup file... +Analyzing configuration backup file... +Processing connection to database... +Analyzing connection to database... +Connecting to /run/postgresql... +Checking database server version... +Checking database server requirements... +Checking user rights... +Checking database availability... +Checking database compatibility level... +Checking database content... +Checking database lock... +Checking database compatibility... + +Query: +Database VeeamBackup is Veeam Backup and Replication configuration database. If you continue, current database contents will be lost. Proceed? +[Yes] [No] +Answer: Yes (OVERWRITE_EXISTING_DATABASE unattended option) + +Stopping all Veeam services and processes... +Analyzing orchestrator service... +Looking for active orchestrated tasks... +Analyzing Veeam Backup and Replication installation... +Analyzing Veeam Backup Enterprise Manager installation... +Analyzing Veeam Backup Catalog installation... +Analyzing AWS Plug-in for Veeam Backup and Replication installation... +Analyzing Microsoft Azure Plug-in for Veeam Backup and Replication installation... +Analyzing Nutanix AHV Plug-in for Veeam Backup and Replication installation... +Analyzing Kasten K10 Plug-in for Veeam Backup & Replication installation... +Analyzing PVE Plug-in for Veeam Backup and Replication installation... +Analyzing running applications... + +Query: +The following Veeam services are running: + +Veeam AWS Service +Veeam Backup REST API Service +Veeam Backup Service +Veeam Broker Service +Veeam Catalog Service +Veeam CDP Coordinator Service +Veeam Cloud Connect +Veeam Data Analyzer Service +Veeam Identity Service +Veeam Kubernetes Service +Veeam Microsoft Azure Platform Service +Veeam Mount Service +Veeam Nutanix AHV Platform Service +Veeam Proxmox Virtual Environment Platform Service +Veeam Threat Hunter +Veeam Web Service + +We need to stop them for now. This may take a moment. Continue? +[Yes] [No] +Answer: Yes (STOP_PROCESSES unattended option) + +Preparing to stop Veeam Identity Service... +Preparing to stop Veeam Web Service... +Preparing to stop Veeam Data Analyzer Service... +Preparing to stop Veeam CDP Coordinator Service... +Preparing to stop Veeam Backup REST API Service... +Preparing to stop Veeam Mount Service... +Preparing to stop Veeam Broker Service... +Preparing to stop Veeam Cloud Connect... +Preparing to stop Veeam Backup Service... +Preparing to stop Veeam Catalog Service... +Preparing to stop Veeam Threat Hunter... +Preparing to stop Veeam AWS Service... +Preparing to stop Veeam Microsoft Azure Platform Service... +Preparing to stop Veeam Nutanix AHV Platform Service... +Preparing to stop Veeam Kubernetes Service... +Preparing to stop Veeam Proxmox Virtual Environment Platform Service... +Configuration restore started at 10/28/2025 6:48:14 PM +Configuration restore mode: restore +Backup file path: /var/lib/veeam/backup/conftoresto.bco +Backup file size: 104.0 KB +Configuration is owned by Veeam Backup and Replication 13.0 +Configuration version: 13.0.0.4967 +Configuration platform: Software Appliance +Product version: 13.0.0.4967 +Database server: PostgreSQL +Server name: /run/postgresql +Database name: VeeamBackup +Empty database: no +Restore session catalog: no +Restore backup catalog: yes +Restore tape catalog: no +Backup database: no +Checking open database connections +Analyzing configuration backup file +Checking configuration backup file +Cleaning up database +Deploying database +Restoring database +Processing configuration VeeamBackup at /run/postgresql +Analyzing database VeeamBackup +Decompressing configuration backup +Connecting to database VeeamBackup +Starting configuration catalog restore +Reading configuration backup +Restoring configuration catalog +Restoring backups catalog +Finalizing configuration catalog restore +Compacting database +Disabling all scheduled user jobs +Adding job sessions to the database... +Locking database VeeamBackup... +Analyzing restored configuration +Restoring backup server certificate +Saving configuration restore session +Configuration restore completed successfully +All backup infrastructure components are up to date +Starting all Veeam services... +Analyzing Veeam Backup and Replication installation... +Analyzing Veeam Backup Enterprise Manager installation... +Analyzing Veeam Backup Catalog installation... +Analyzing AWS Plug-in for Veeam Backup and Replication installation... +Analyzing Microsoft Azure Plug-in for Veeam Backup and Replication installation... +Analyzing Nutanix AHV Plug-in for Veeam Backup and Replication installation... +Analyzing Kasten K10 Plug-in for Veeam Backup & Replication installation... +Analyzing PVE Plug-in for Veeam Backup and Replication installation... +Preparing to start Veeam AWS Service... +Preparing to start Veeam Microsoft Azure Platform Service... +Preparing to start Veeam Nutanix AHV Platform Service... +Preparing to start Veeam Kubernetes Service... +Preparing to start Veeam Proxmox Virtual Environment Platform Service... +Preparing to start Veeam Threat Hunter... +Preparing to start Veeam Catalog Service... +Preparing to start Veeam Backup Service... +Preparing to start Veeam Cloud Connect... +Preparing to start Veeam Mount Service... +Preparing to start Veeam Broker Service... +Preparing to start Veeam Backup REST API Service... +Preparing to start Veeam CDP Coordinator Service... +Preparing to start Veeam Data Analyzer Service... +Preparing to start Veeam Web Service... +Preparing to launch the application... +Starting backup repositories and replica hosts rescan... + +Configuration restore completed successfully at 10/28/2025 6:49:13 PM +OK : Configuration restored +Additional logs: + - Password SO config: /var/log/veeam_addsoconfpw.log + - Config restore: /var/log/veeam_configrestore.log +Cleaning up oathtool curl and rm unattended.xml veeam_addsoconfpw.sh ... +Dependencies resolved. +================================================================================ + Package Architecture Version Repository Size +================================================================================ +Removing: + oathtool x86_64 2.6.12-1.el9 @epel 90 k +Removing unused dependencies: + liboath x86_64 2.6.12-1.el9 @epel 94 k + +Transaction Summary +================================================================================ +Remove 2 Packages + +Freed space: 183 k +Running transaction check +Transaction check succeeded. +Running transaction test +Transaction test succeeded. +Running transaction + Preparing : 1/1 + Erasing : oathtool-2.6.12-1.el9.x86_64 1/2 + Erasing : liboath-2.6.12-1.el9.x86_64 2/2 + Running scriptlet: liboath-2.6.12-1.el9.x86_64 2/2 + Verifying : liboath-2.6.12-1.el9.x86_64 1/2 + Verifying : oathtool-2.6.12-1.el9.x86_64 2/2 +Installed products updated. + +Removed: + liboath-2.6.12-1.el9.x86_64 oathtool-2.6.12-1.el9.x86_64 + +Complete! +16 files removed +Disabling veeam-init service... +Removed "/etc/systemd/system/multi-user.target.wants/veeam-init.service". +OK : Service disabled +=========================================== +Veeam VSA Initialization Completed Successfully +=========================================== +All logs consolidated in: /var/log/veeam_init.log +=========================================== diff --git a/VSA-Autodeploy/offline_repo/alternatives-1.24-2.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/alternatives-1.24-2.el9.x86_64.rpm new file mode 100644 index 0000000..d53d3a8 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/alternatives-1.24-2.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/audit-libs-3.1.5-4.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/audit-libs-3.1.5-4.el9.x86_64.rpm new file mode 100644 index 0000000..cbd0186 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/audit-libs-3.1.5-4.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/basesystem-11-13.el9.0.1.noarch.rpm b/VSA-Autodeploy/offline_repo/basesystem-11-13.el9.0.1.noarch.rpm new file mode 100644 index 0000000..e2065fb Binary files /dev/null and b/VSA-Autodeploy/offline_repo/basesystem-11-13.el9.0.1.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/bash-5.1.8-9.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/bash-5.1.8-9.el9.x86_64.rpm new file mode 100644 index 0000000..824c9dc Binary files /dev/null and b/VSA-Autodeploy/offline_repo/bash-5.1.8-9.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/bzip2-libs-1.0.8-10.el9_5.x86_64.rpm b/VSA-Autodeploy/offline_repo/bzip2-libs-1.0.8-10.el9_5.x86_64.rpm new file mode 100644 index 0000000..0b12f31 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/bzip2-libs-1.0.8-10.el9_5.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/ca-certificates-2024.2.69_v8.0.303-91.4.el9_4.noarch.rpm b/VSA-Autodeploy/offline_repo/ca-certificates-2024.2.69_v8.0.303-91.4.el9_4.noarch.rpm new file mode 100644 index 0000000..27741d4 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/ca-certificates-2024.2.69_v8.0.303-91.4.el9_4.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/coreutils-8.32-39.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/coreutils-8.32-39.el9.x86_64.rpm new file mode 100644 index 0000000..f651d67 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/coreutils-8.32-39.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/coreutils-common-8.32-39.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/coreutils-common-8.32-39.el9.x86_64.rpm new file mode 100644 index 0000000..1d0f2db Binary files /dev/null and b/VSA-Autodeploy/offline_repo/coreutils-common-8.32-39.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/crypto-policies-20250128-1.git5269e22.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/crypto-policies-20250128-1.git5269e22.el9.noarch.rpm new file mode 100644 index 0000000..f90bdb1 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/crypto-policies-20250128-1.git5269e22.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/curl-7.76.1-31.el9_6.1.x86_64.rpm b/VSA-Autodeploy/offline_repo/curl-7.76.1-31.el9_6.1.x86_64.rpm new file mode 100644 index 0000000..e120bf7 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/curl-7.76.1-31.el9_6.1.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/cyrus-sasl-lib-2.1.27-21.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/cyrus-sasl-lib-2.1.27-21.el9.x86_64.rpm new file mode 100644 index 0000000..c2fc6d4 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/cyrus-sasl-lib-2.1.27-21.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/filesystem-3.16-5.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/filesystem-3.16-5.el9.x86_64.rpm new file mode 100644 index 0000000..caaf923 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/filesystem-3.16-5.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/gawk-5.1.0-6.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/gawk-5.1.0-6.el9.x86_64.rpm new file mode 100644 index 0000000..fd18433 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/gawk-5.1.0-6.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/gawk-all-langpacks-5.1.0-6.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/gawk-all-langpacks-5.1.0-6.el9.x86_64.rpm new file mode 100644 index 0000000..f61e6c6 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/gawk-all-langpacks-5.1.0-6.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/gdbm-libs-1.23-1.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/gdbm-libs-1.23-1.el9.x86_64.rpm new file mode 100644 index 0000000..2d0a664 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/gdbm-libs-1.23-1.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/glibc-2.34-168.el9_6.23.x86_64.rpm b/VSA-Autodeploy/offline_repo/glibc-2.34-168.el9_6.23.x86_64.rpm new file mode 100644 index 0000000..9a98468 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/glibc-2.34-168.el9_6.23.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/glibc-common-2.34-168.el9_6.23.x86_64.rpm b/VSA-Autodeploy/offline_repo/glibc-common-2.34-168.el9_6.23.x86_64.rpm new file mode 100644 index 0000000..eb30845 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/glibc-common-2.34-168.el9_6.23.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/glibc-gconv-extra-2.34-168.el9_6.23.x86_64.rpm b/VSA-Autodeploy/offline_repo/glibc-gconv-extra-2.34-168.el9_6.23.x86_64.rpm new file mode 100644 index 0000000..d74403e Binary files /dev/null and b/VSA-Autodeploy/offline_repo/glibc-gconv-extra-2.34-168.el9_6.23.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/glibc-minimal-langpack-2.34-168.el9_6.23.x86_64.rpm b/VSA-Autodeploy/offline_repo/glibc-minimal-langpack-2.34-168.el9_6.23.x86_64.rpm new file mode 100644 index 0000000..42a6f4b Binary files /dev/null and b/VSA-Autodeploy/offline_repo/glibc-minimal-langpack-2.34-168.el9_6.23.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/gmp-6.2.0-13.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/gmp-6.2.0-13.el9.x86_64.rpm new file mode 100644 index 0000000..bf86acf Binary files /dev/null and b/VSA-Autodeploy/offline_repo/gmp-6.2.0-13.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/grep-3.6-5.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/grep-3.6-5.el9.x86_64.rpm new file mode 100644 index 0000000..2540797 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/grep-3.6-5.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/keyutils-libs-1.6.3-1.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/keyutils-libs-1.6.3-1.el9.x86_64.rpm new file mode 100644 index 0000000..883013b Binary files /dev/null and b/VSA-Autodeploy/offline_repo/keyutils-libs-1.6.3-1.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/krb5-libs-1.21.1-8.el9_6.x86_64.rpm b/VSA-Autodeploy/offline_repo/krb5-libs-1.21.1-8.el9_6.x86_64.rpm new file mode 100644 index 0000000..b9d5097 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/krb5-libs-1.21.1-8.el9_6.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libacl-2.3.1-4.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libacl-2.3.1-4.el9.x86_64.rpm new file mode 100644 index 0000000..81ebf75 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libacl-2.3.1-4.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libattr-2.5.1-3.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libattr-2.5.1-3.el9.x86_64.rpm new file mode 100644 index 0000000..b627149 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libattr-2.5.1-3.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libbrotli-1.0.9-7.el9_5.x86_64.rpm b/VSA-Autodeploy/offline_repo/libbrotli-1.0.9-7.el9_5.x86_64.rpm new file mode 100644 index 0000000..4a67bc0 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libbrotli-1.0.9-7.el9_5.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libcap-2.48-9.el9_2.x86_64.rpm b/VSA-Autodeploy/offline_repo/libcap-2.48-9.el9_2.x86_64.rpm new file mode 100644 index 0000000..b59e543 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libcap-2.48-9.el9_2.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libcap-ng-0.8.2-7.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libcap-ng-0.8.2-7.el9.x86_64.rpm new file mode 100644 index 0000000..c6f3834 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libcap-ng-0.8.2-7.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libcom_err-1.46.5-7.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libcom_err-1.46.5-7.el9.x86_64.rpm new file mode 100644 index 0000000..9787227 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libcom_err-1.46.5-7.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libcurl-7.76.1-31.el9_6.1.x86_64.rpm b/VSA-Autodeploy/offline_repo/libcurl-7.76.1-31.el9_6.1.x86_64.rpm new file mode 100644 index 0000000..c12c0d9 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libcurl-7.76.1-31.el9_6.1.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libevent-2.1.12-8.el9_4.x86_64.rpm b/VSA-Autodeploy/offline_repo/libevent-2.1.12-8.el9_4.x86_64.rpm new file mode 100644 index 0000000..11e5fdb Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libevent-2.1.12-8.el9_4.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libffi-3.4.2-8.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libffi-3.4.2-8.el9.x86_64.rpm new file mode 100644 index 0000000..a963246 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libffi-3.4.2-8.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libgcc-11.5.0-5.el9_5.x86_64.rpm b/VSA-Autodeploy/offline_repo/libgcc-11.5.0-5.el9_5.x86_64.rpm new file mode 100644 index 0000000..c5102d8 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libgcc-11.5.0-5.el9_5.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libidn2-2.3.0-7.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libidn2-2.3.0-7.el9.x86_64.rpm new file mode 100644 index 0000000..3f7e7be Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libidn2-2.3.0-7.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libnghttp2-1.43.0-6.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libnghttp2-1.43.0-6.el9.x86_64.rpm new file mode 100644 index 0000000..c5ad6d6 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libnghttp2-1.43.0-6.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/liboath-2.6.12-1.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/liboath-2.6.12-1.el9.x86_64.rpm new file mode 100644 index 0000000..f685b5c Binary files /dev/null and b/VSA-Autodeploy/offline_repo/liboath-2.6.12-1.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libpsl-0.21.1-5.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libpsl-0.21.1-5.el9.x86_64.rpm new file mode 100644 index 0000000..2d05fd6 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libpsl-0.21.1-5.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libselinux-3.6-3.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libselinux-3.6-3.el9.x86_64.rpm new file mode 100644 index 0000000..a970b3b Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libselinux-3.6-3.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libsemanage-3.6-5.el9_6.x86_64.rpm b/VSA-Autodeploy/offline_repo/libsemanage-3.6-5.el9_6.x86_64.rpm new file mode 100644 index 0000000..6996750 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libsemanage-3.6-5.el9_6.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libsepol-3.6-2.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libsepol-3.6-2.el9.x86_64.rpm new file mode 100644 index 0000000..cf6c88d Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libsepol-3.6-2.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libsigsegv-2.13-4.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libsigsegv-2.13-4.el9.x86_64.rpm new file mode 100644 index 0000000..8437bbf Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libsigsegv-2.13-4.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libssh-0.10.4-15.el9_6.x86_64.rpm b/VSA-Autodeploy/offline_repo/libssh-0.10.4-15.el9_6.x86_64.rpm new file mode 100644 index 0000000..e5bc506 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libssh-0.10.4-15.el9_6.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libssh-config-0.10.4-15.el9_6.noarch.rpm b/VSA-Autodeploy/offline_repo/libssh-config-0.10.4-15.el9_6.noarch.rpm new file mode 100644 index 0000000..0929b1e Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libssh-config-0.10.4-15.el9_6.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libtasn1-4.16.0-9.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libtasn1-4.16.0-9.el9.x86_64.rpm new file mode 100644 index 0000000..7b6c86e Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libtasn1-4.16.0-9.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libtool-ltdl-2.4.6-46.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libtool-ltdl-2.4.6-46.el9.x86_64.rpm new file mode 100644 index 0000000..4c3b4b6 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libtool-ltdl-2.4.6-46.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libunistring-0.9.10-15.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libunistring-0.9.10-15.el9.x86_64.rpm new file mode 100644 index 0000000..bc31f11 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libunistring-0.9.10-15.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libverto-0.3.2-3.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libverto-0.3.2-3.el9.x86_64.rpm new file mode 100644 index 0000000..b46dea1 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libverto-0.3.2-3.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/libxcrypt-4.4.18-3.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/libxcrypt-4.4.18-3.el9.x86_64.rpm new file mode 100644 index 0000000..37a362e Binary files /dev/null and b/VSA-Autodeploy/offline_repo/libxcrypt-4.4.18-3.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/mpfr-4.1.0-7.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/mpfr-4.1.0-7.el9.x86_64.rpm new file mode 100644 index 0000000..9f56519 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/mpfr-4.1.0-7.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/ncurses-base-6.2-10.20210508.el9_6.2.noarch.rpm b/VSA-Autodeploy/offline_repo/ncurses-base-6.2-10.20210508.el9_6.2.noarch.rpm new file mode 100644 index 0000000..9c85d40 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/ncurses-base-6.2-10.20210508.el9_6.2.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/ncurses-libs-6.2-10.20210508.el9_6.2.x86_64.rpm b/VSA-Autodeploy/offline_repo/ncurses-libs-6.2-10.20210508.el9_6.2.x86_64.rpm new file mode 100644 index 0000000..1fd8b11 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/ncurses-libs-6.2-10.20210508.el9_6.2.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/oathtool-2.6.12-1.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/oathtool-2.6.12-1.el9.x86_64.rpm new file mode 100644 index 0000000..d5e9162 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/oathtool-2.6.12-1.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/openldap-2.6.8-4.el9.0.1.x86_64.rpm b/VSA-Autodeploy/offline_repo/openldap-2.6.8-4.el9.0.1.x86_64.rpm new file mode 100644 index 0000000..ccbbe3e Binary files /dev/null and b/VSA-Autodeploy/offline_repo/openldap-2.6.8-4.el9.0.1.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/openssl-libs-3.2.2-6.el9_5.1.x86_64.rpm b/VSA-Autodeploy/offline_repo/openssl-libs-3.2.2-6.el9_5.1.x86_64.rpm new file mode 100644 index 0000000..cd998ac Binary files /dev/null and b/VSA-Autodeploy/offline_repo/openssl-libs-3.2.2-6.el9_5.1.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/p11-kit-0.25.3-3.el9_5.x86_64.rpm b/VSA-Autodeploy/offline_repo/p11-kit-0.25.3-3.el9_5.x86_64.rpm new file mode 100644 index 0000000..503251f Binary files /dev/null and b/VSA-Autodeploy/offline_repo/p11-kit-0.25.3-3.el9_5.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/p11-kit-trust-0.25.3-3.el9_5.x86_64.rpm b/VSA-Autodeploy/offline_repo/p11-kit-trust-0.25.3-3.el9_5.x86_64.rpm new file mode 100644 index 0000000..94a8901 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/p11-kit-trust-0.25.3-3.el9_5.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/pcre-8.44-4.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/pcre-8.44-4.el9.x86_64.rpm new file mode 100644 index 0000000..2fa6e45 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/pcre-8.44-4.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/pcre2-10.40-6.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/pcre2-10.40-6.el9.x86_64.rpm new file mode 100644 index 0000000..1a60657 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/pcre2-10.40-6.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/pcre2-syntax-10.40-6.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/pcre2-syntax-10.40-6.el9.noarch.rpm new file mode 100644 index 0000000..fa7556f Binary files /dev/null and b/VSA-Autodeploy/offline_repo/pcre2-syntax-10.40-6.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm new file mode 100644 index 0000000..42fb876 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/readline-8.1-4.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/readline-8.1-4.el9.x86_64.rpm new file mode 100644 index 0000000..9b3fe56 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/readline-8.1-4.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/repodata/08c34b610e220d11f28b256ca9037ae7497adb62f4156550c6898505c2490efd-other.xml.gz b/VSA-Autodeploy/offline_repo/repodata/08c34b610e220d11f28b256ca9037ae7497adb62f4156550c6898505c2490efd-other.xml.gz new file mode 100644 index 0000000..4b264d9 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/repodata/08c34b610e220d11f28b256ca9037ae7497adb62f4156550c6898505c2490efd-other.xml.gz differ diff --git a/VSA-Autodeploy/offline_repo/repodata/1d6ffa20f991d2f8b247bf23d969a7c08629504796aae5d527e4d10ef8162f70-filelists.xml.gz b/VSA-Autodeploy/offline_repo/repodata/1d6ffa20f991d2f8b247bf23d969a7c08629504796aae5d527e4d10ef8162f70-filelists.xml.gz new file mode 100644 index 0000000..48f5dfe Binary files /dev/null and b/VSA-Autodeploy/offline_repo/repodata/1d6ffa20f991d2f8b247bf23d969a7c08629504796aae5d527e4d10ef8162f70-filelists.xml.gz differ diff --git a/VSA-Autodeploy/offline_repo/repodata/33296ce002c60a823dd3160581da9f938b26a1eea144fd5fa6072bc81891f3d0-filelists.sqlite.bz2 b/VSA-Autodeploy/offline_repo/repodata/33296ce002c60a823dd3160581da9f938b26a1eea144fd5fa6072bc81891f3d0-filelists.sqlite.bz2 new file mode 100644 index 0000000..ed057eb Binary files /dev/null and b/VSA-Autodeploy/offline_repo/repodata/33296ce002c60a823dd3160581da9f938b26a1eea144fd5fa6072bc81891f3d0-filelists.sqlite.bz2 differ diff --git a/VSA-Autodeploy/offline_repo/repodata/abcd3eb01e748ef8a662caceb51f9a5e3eb4aba0ea23ecbdc4496c85e994c083-primary.xml.gz b/VSA-Autodeploy/offline_repo/repodata/abcd3eb01e748ef8a662caceb51f9a5e3eb4aba0ea23ecbdc4496c85e994c083-primary.xml.gz new file mode 100644 index 0000000..9cbbee8 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/repodata/abcd3eb01e748ef8a662caceb51f9a5e3eb4aba0ea23ecbdc4496c85e994c083-primary.xml.gz differ diff --git a/VSA-Autodeploy/offline_repo/repodata/acd3999b20855ebf9c48a9e6ddb07bff666479005a5dd8379ec623d6bc5eaeaf-primary.sqlite.bz2 b/VSA-Autodeploy/offline_repo/repodata/acd3999b20855ebf9c48a9e6ddb07bff666479005a5dd8379ec623d6bc5eaeaf-primary.sqlite.bz2 new file mode 100644 index 0000000..54c17ea Binary files /dev/null and b/VSA-Autodeploy/offline_repo/repodata/acd3999b20855ebf9c48a9e6ddb07bff666479005a5dd8379ec623d6bc5eaeaf-primary.sqlite.bz2 differ diff --git a/VSA-Autodeploy/offline_repo/repodata/d2ec0afce664410bde48015f0091cd0e785a3bf8aeef6db10e2020d42e169139-other.sqlite.bz2 b/VSA-Autodeploy/offline_repo/repodata/d2ec0afce664410bde48015f0091cd0e785a3bf8aeef6db10e2020d42e169139-other.sqlite.bz2 new file mode 100644 index 0000000..d30a8d3 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/repodata/d2ec0afce664410bde48015f0091cd0e785a3bf8aeef6db10e2020d42e169139-other.sqlite.bz2 differ diff --git a/VSA-Autodeploy/offline_repo/repodata/repomd.xml b/VSA-Autodeploy/offline_repo/repodata/repomd.xml new file mode 100644 index 0000000..9778456 --- /dev/null +++ b/VSA-Autodeploy/offline_repo/repodata/repomd.xml @@ -0,0 +1,55 @@ + + + 1761758892 + + abcd3eb01e748ef8a662caceb51f9a5e3eb4aba0ea23ecbdc4496c85e994c083 + b445ec8ce0fbbdca7152b4a230fb8f349d24ee0213d00ee409c93b6482ef7801 + + 1761758892 + 32786 + 208644 + + + 1d6ffa20f991d2f8b247bf23d969a7c08629504796aae5d527e4d10ef8162f70 + 036b8073680e9ad26b0655ee3d99b37666d2013b8f508cf40ee7ba54b7f4161e + + 1761758892 + 104341 + 1216121 + + + 08c34b610e220d11f28b256ca9037ae7497adb62f4156550c6898505c2490efd + 2dc69a9956f053f864c6986d38d02c13dccd534673d67614e70aff81217b93d7 + + 1761758892 + 23182 + 107665 + + + acd3999b20855ebf9c48a9e6ddb07bff666479005a5dd8379ec623d6bc5eaeaf + d5650d7bd7997eee28707624ac1e7716283cdcbfcebf8e3e0ddf6b02ee386463 + + 1761758892 + 61776 + 352256 + 10 + + + 33296ce002c60a823dd3160581da9f938b26a1eea144fd5fa6072bc81891f3d0 + 28fc1d106b7dca6b33479058ba41b00d72798da50fbf1fb35678b8603a410ebf + + 1761758892 + 91211 + 462848 + 10 + + + d2ec0afce664410bde48015f0091cd0e785a3bf8aeef6db10e2020d42e169139 + 04ea888db281b78c945d1c231e142728c541bf427476439e9f2b2605e5f9cf93 + + 1761758892 + 29328 + 122880 + 10 + + diff --git a/VSA-Autodeploy/offline_repo/rocky-gpg-keys-9.6-1.3.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/rocky-gpg-keys-9.6-1.3.el9.noarch.rpm new file mode 100644 index 0000000..08051b2 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/rocky-gpg-keys-9.6-1.3.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/rocky-release-9.6-1.3.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/rocky-release-9.6-1.3.el9.noarch.rpm new file mode 100644 index 0000000..b2a130b Binary files /dev/null and b/VSA-Autodeploy/offline_repo/rocky-release-9.6-1.3.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/rocky-repos-9.6-1.3.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/rocky-repos-9.6-1.3.el9.noarch.rpm new file mode 100644 index 0000000..39c6cf7 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/rocky-repos-9.6-1.3.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/sed-4.8-9.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/sed-4.8-9.el9.x86_64.rpm new file mode 100644 index 0000000..5d68256 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/sed-4.8-9.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/setup-2.13.7-10.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/setup-2.13.7-10.el9.noarch.rpm new file mode 100644 index 0000000..066a45c Binary files /dev/null and b/VSA-Autodeploy/offline_repo/setup-2.13.7-10.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/shadow-utils-4.9-12.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/shadow-utils-4.9-12.el9.x86_64.rpm new file mode 100644 index 0000000..80249fc Binary files /dev/null and b/VSA-Autodeploy/offline_repo/shadow-utils-4.9-12.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/offline_repo/tzdata-2025b-1.el9.noarch.rpm b/VSA-Autodeploy/offline_repo/tzdata-2025b-1.el9.noarch.rpm new file mode 100644 index 0000000..61773f9 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/tzdata-2025b-1.el9.noarch.rpm differ diff --git a/VSA-Autodeploy/offline_repo/zlib-1.2.11-40.el9.x86_64.rpm b/VSA-Autodeploy/offline_repo/zlib-1.2.11-40.el9.x86_64.rpm new file mode 100644 index 0000000..b1a2133 Binary files /dev/null and b/VSA-Autodeploy/offline_repo/zlib-1.2.11-40.el9.x86_64.rpm differ diff --git a/VSA-Autodeploy/powershell/AutoProvisionning.ps1 b/VSA-Autodeploy/powershell/AutoProvisionning.ps1 new file mode 100644 index 0000000..0069cd3 --- /dev/null +++ b/VSA-Autodeploy/powershell/AutoProvisionning.ps1 @@ -0,0 +1,246 @@ +#Requires -Version 5.1 +#Requires -Modules Hyper-V + +<# +.SYNOPSIS + Mounts ISOs to Hyper-V VMs and boots them from DVD for QA testing. + +.DESCRIPTION + Automates the process of mounting ISO images to multiple Hyper-V VMs, + setting boot order to DVD, and starting the VMs. + +.PARAMETER ConfigFile + Path to a CSV file containing VM configurations (Name, ISOPath columns). + +.PARAMETER ISOBasePath + Base directory for ISO files (optional, for relative paths). + +.PARAMETER TranscriptPath + Path for transcript logging (default: script directory\Logs). + +.EXAMPLE + .\QA.ps1 -ConfigFile ".\vm-config.csv" + +.EXAMPLE + # Default configuration (hardcoded VMs) + .\QA.ps1 + +.NOTES + Author: IT Operations + Version: 2.1 + Requires: Hyper-V PowerShell Module, Administrator privileges +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory=$false, HelpMessage="Path to CSV configuration file")] + [ValidateScript({ + if (-not (Test-Path $_ -PathType Leaf)) { + throw "Configuration file not found: $_" + } + $true + })] + [string]$ConfigFile, + + [Parameter(Mandatory=$false, HelpMessage="Base directory for ISO files")] + [string]$ISOBasePath, + + [Parameter(Mandatory=$false, HelpMessage="Path for transcript logging")] + [string]$TranscriptPath = (Join-Path $PSScriptRoot "Logs") +) + +# ========================================== +# INITIALIZATION +# ========================================== +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Start transcript +if (-not (Test-Path $TranscriptPath)) { + New-Item -Path $TranscriptPath -ItemType Directory -Force | Out-Null +} +$transcriptFile = Join-Path $TranscriptPath ("HyperV-QA_" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".log") +Start-Transcript -Path $transcriptFile -Append + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Hyper-V VM QA Provisioning" -ForegroundColor Cyan +Write-Host " Version 2.1" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan + +# ========================================== +# DEFAULT CONFIGURATION +# ========================================== +$defaultVMConfigs = @( + @{Name="VBRv13DHCP"; ISOPath="K:\autodeploy vsa\test\DHCP_13.0.1.180_20251101.iso"}, + @{Name="VBRv13Static"; ISOPath="K:\autodeploy vsa\test\static.iso"}, + @{Name="VIAPROXY"; ISOPath="K:\autodeploy vsa\test\VIA_13.0.1.180_20251101.iso"}, + @{Name="VeeamJEOSVHR13"; ISOPath="K:\autodeploy vsa\test\VHR_13.0.1.180_20251101.iso"} +) + +# ========================================== +# LOAD CONFIGURATION +# ========================================== +if ($ConfigFile) { + Write-Host "`nLoading configuration from CSV: $ConfigFile" -ForegroundColor Cyan + try { + $csvData = Import-Csv -Path $ConfigFile -ErrorAction Stop + + $VMConfigs = $csvData | ForEach-Object { + @{ + Name = $_.Name + ISOPath = $_.ISOPath + } + } + + Write-Host " ✓ Loaded $($VMConfigs.Count) VM configurations" -ForegroundColor Green + } catch { + Write-Host " ✗ Failed to load CSV: $_" -ForegroundColor Red + Stop-Transcript + exit 1 + } +} else { + Write-Host "`nUsing default VM configurations" -ForegroundColor Cyan + $VMConfigs = $defaultVMConfigs + Write-Host " ✓ Processing $($VMConfigs.Count) VMs" -ForegroundColor Green +} + +# ========================================== +# PROCESS EACH VM +# ========================================== +Write-Host "`nProvisioning VMs..." -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan + +$results = @() + +foreach ($config in $VMConfigs) { + Write-Host "`nVM: $($config.Name)" -ForegroundColor Yellow + + $result = [PSCustomObject]@{ + VMName = $config.Name + Success = $false + Message = "" + ISOPath = $config.ISOPath + Actions = @() + } + + try { + # Resolve ISO path + $isoPath = $config.ISOPath + if ($ISOBasePath -and -not [System.IO.Path]::IsPathRooted($isoPath)) { + $isoPath = Join-Path $ISOBasePath $isoPath + } + + # Validate ISO exists + if (-not (Test-Path $isoPath -PathType Leaf)) { + throw "ISO file not found: $isoPath" + } + Write-Host " ✓ ISO found: $isoPath" -ForegroundColor Gray + + # Validate VM exists + $vm = Get-VM -Name $config.Name -ErrorAction Stop + Write-Host " ✓ VM found (Generation $($vm.Generation), State: $($vm.State))" -ForegroundColor Gray + + # Check if VM is already running + if ($vm.State -eq 'Running') { + Write-Host " ⊘ VM is already running, skipping" -ForegroundColor Yellow + $result.Success = $true + $result.Message = "Already running" + $results += $result + continue + } + + # Get or add DVD drive + $dvdDrive = Get-VMDvdDrive -VMName $config.Name -ErrorAction SilentlyContinue + + if (-not $dvdDrive) { + if ($PSCmdlet.ShouldProcess($config.Name, "Add DVD drive and mount ISO")) { + Add-VMDvdDrive -VMName $config.Name -Path $isoPath -ErrorAction Stop + Write-Host " ✓ DVD drive added and ISO mounted" -ForegroundColor Green + $result.Actions += "DVD drive added" + } + } else { + if ($PSCmdlet.ShouldProcess($config.Name, "Mount ISO to existing DVD drive")) { + Set-VMDvdDrive -VMName $config.Name -Path $isoPath -ErrorAction Stop + Write-Host " ✓ ISO mounted to existing DVD drive" -ForegroundColor Green + $result.Actions += "ISO mounted" + } + } + + # Get DVD drive reference + $dvd = Get-VMDvdDrive -VMName $config.Name -ErrorAction Stop + + # Set boot order (Generation 2 only) + if ($vm.Generation -eq 2) { + if ($PSCmdlet.ShouldProcess($config.Name, "Set DVD as first boot device")) { + Set-VMFirmware -VMName $config.Name -FirstBootDevice $dvd -ErrorAction Stop + Write-Host " ✓ Boot order set: DVD first" -ForegroundColor Green + $result.Actions += "Boot order configured" + } + } else { + Write-Host " ⓘ Generation 1 VM: Boot order managed via BIOS" -ForegroundColor Gray + $result.Actions += "Gen1 (BIOS boot)" + } + + # Start the VM + if ($PSCmdlet.ShouldProcess($config.Name, "Start VM")) { + Start-VM -Name $config.Name -ErrorAction Stop + Write-Host " ✓ VM started successfully" -ForegroundColor Green + $result.Actions += "VM started" + } + + $result.Success = $true + $result.Message = "Provisioned successfully" + + } catch { + Write-Host " ✗ ERROR: $_" -ForegroundColor Red + $result.Success = $false + $result.Message = $_.Exception.Message + } + + $results += $result +} + +# ========================================== +# SUMMARY +# ========================================== +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host " Provisioning Summary" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan + +$successCount = @($results | Where-Object Success).Count +$failCount = @($results | Where-Object { -not $_.Success }).Count + +Write-Host "`nTotal VMs: $($results.Count)" -ForegroundColor Gray +Write-Host "Successful: $successCount" -ForegroundColor Green +Write-Host "Failed: $failCount" -ForegroundColor $(if($failCount -gt 0){'Red'}else{'Green'}) + +Write-Host "`nDetailed Results:" -ForegroundColor Gray +foreach ($result in $results) { + $color = if ($result.Success) { 'Green' } else { 'Red' } + $symbol = if ($result.Success) { '✓' } else { '✗' } + + Write-Host " $symbol $($result.VMName): $($result.Message)" -ForegroundColor $color + + # Safe array handling for Actions + $actionArray = @($result.Actions) + if ($actionArray.Count -gt 0) { + Write-Host " Actions: $($actionArray -join ', ')" -ForegroundColor Gray + } +} + +if ($failCount -gt 0) { + Write-Host "`nFailed VMs require attention:" -ForegroundColor Red + $results | Where-Object { -not $_.Success } | ForEach-Object { + Write-Host " - $($_.VMName): $($_.Message)" -ForegroundColor Red + } +} + +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host " Script completed!" -ForegroundColor Green +Write-Host " Transcript: $transcriptFile" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan + +Stop-Transcript + +# Exit with appropriate code +exit $(if ($failCount -gt 0) { 1 } else { 0 }) diff --git a/VSA-Autodeploy/powershell/Create-ISO.ps1 b/VSA-Autodeploy/powershell/Create-ISO.ps1 new file mode 100644 index 0000000..d17f3ea --- /dev/null +++ b/VSA-Autodeploy/powershell/Create-ISO.ps1 @@ -0,0 +1,18 @@ +# Run sequentially +$jsonFiles = @( + ".\viaproxy.json", + ".\dhcp.json", + ".\vhr.json" +) + +foreach ($config in $jsonFiles) { + Write-Host "`n========================================" -ForegroundColor Cyan + Write-Host "Processing: $config" -ForegroundColor Cyan + Write-Host "========================================`n" -ForegroundColor Cyan + + .\autodeploy.ps1 -ConfigFile $config + + if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: $config failed with exit code $LASTEXITCODE" -ForegroundColor Red + } +} diff --git a/VSA-Autodeploy/powershell/Install-VeeamInfra.ps1 b/VSA-Autodeploy/powershell/Install-VeeamInfra.ps1 new file mode 100644 index 0000000..2e4f8fb --- /dev/null +++ b/VSA-Autodeploy/powershell/Install-VeeamInfra.ps1 @@ -0,0 +1,598 @@ +#Requires -Version 5.1 +#Requires -RunAsAdministrator + +<# +.SYNOPSIS + Installs Veeam License, Adds Linux Proxy and Hardened Repository. + +.DESCRIPTION + Connects to a VBR server, applies a license, and registers Linux infrastructure components. + Retries connection for up to 45 minutes if the VBR server is unavailable. + +.PARAMETER VbrServer + IP address or hostname of the VBR server + +.PARAMETER Credential + PSCredential object for VBR authentication + +.PARAMETER VbrUsername + Username for VBR (used if Credential not provided) + +.PARAMETER ProxyIP + IP address of the Linux Proxy (JeOS) + +.PARAMETER RepositoryIP + IP address of the Linux Hardened Repository + +.PARAMETER RepositoryName + Name for the backup repository in VBR + +.PARAMETER PairingCodeProxy + Pairing code for the Proxy appliance + +.PARAMETER PairingCodeRepo + Pairing code for the Repository appliance + +.PARAMETER LicenseFilePath + Full path to the license file + +.PARAMETER ImmutabilityPeriod + Immutability period in days (1-365) + +.PARAMETER RetryInterval + Seconds to wait between connection attempts + +.PARAMETER Timeout + Total timeout in seconds for connection attempts + +.PARAMETER ProxyMaxTasks + Maximum concurrent tasks for the proxy + +.PARAMETER TranscriptPath + Path for transcript logging (default: script directory) + +.EXAMPLE + $cred = Get-Credential + .\Install-VeeamInfra.ps1 -VbrServer "192.168.1.168" -Credential $cred ` + -ProxyIP "192.168.1.141" -RepositoryIP "192.168.1.122" ` + -RepositoryName "HardenedRepo-122" -ImmutabilityPeriod 30 ` + -LicenseFilePath "K:\autodeploy vsa\test\license\Veeam-100instances-entplus-monitoring-nfr.lic" + + + + +.NOTES + Author: Infrastructure Team + Version: 2.0 + Requires: Veeam Backup & Replication PowerShell Module +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$true, HelpMessage="IP address or hostname of the VBR server")] + [ValidateNotNullOrEmpty()] + [string]$VbrServer, + + [Parameter(Mandatory=$false, HelpMessage="Username for VBR server")] + [string]$VbrUsername, + + [Parameter(Mandatory=$false, HelpMessage="Password for VBR server")] + [string]$VbrPassword, + + [Parameter(Mandatory=$false, HelpMessage="IP of the Linux Proxy (JeOS)")] + [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$')] + [string]$ProxyIP, + + [Parameter(Mandatory=$false, HelpMessage="IP of the Linux Hardened Repository (JeOS)")] + [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$')] + [string]$RepositoryIP, + + [Parameter(Mandatory=$false, HelpMessage="Name for the Backup Repository in VBR")] + [ValidateLength(1,255)] + [string]$RepositoryName, + + [Parameter(Mandatory=$false, HelpMessage="Pairing code for the Proxy appliance")] + [ValidatePattern('^\d{6}$')] + [string]$PairingCodeProxy = "000000", + + [Parameter(Mandatory=$false, HelpMessage="Pairing code for the Repository appliance")] + [ValidatePattern('^\d{6}$')] + [string]$PairingCodeRepo = "000000", + + [Parameter(Mandatory=$false, HelpMessage="Full path to the license file")] + [ValidateScript({ + if ($_ -and -not (Test-Path $_ -PathType Leaf)) { + throw "License file not found: $_" + } + $true + })] + [string]$LicenseFilePath, + + [Parameter(Mandatory=$false, HelpMessage="Immutability period in days")] + [ValidateRange(1,365)] + [int]$ImmutabilityPeriod = 21, + + [Parameter(Mandatory=$false, HelpMessage="Seconds to wait between connection attempts")] + [ValidateRange(30,600)] + [int]$RetryInterval = 120, + + [Parameter(Mandatory=$false, HelpMessage="Total timeout in seconds for connection attempts")] + [ValidateRange(300,7200)] + [int]$Timeout = 2700, + + [Parameter(Mandatory=$false, HelpMessage="Maximum concurrent tasks for the proxy")] + [ValidateRange(1,32)] + [int]$ProxyMaxTasks = 4, + + [Parameter(Mandatory=$false, HelpMessage="Path for transcript logging")] + [string]$TranscriptPath = (Join-Path $PSScriptRoot "Logs") +) + +# ========================================== +# INITIALIZATION +# ========================================== +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Start transcript logging +if (-not (Test-Path $TranscriptPath)) { + New-Item -Path $TranscriptPath -ItemType Directory -Force | Out-Null +} +$transcriptFile = Join-Path $TranscriptPath ("VeeamDeploy_" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".log") +Start-Transcript -Path $transcriptFile -Append + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Veeam B&R - Infrastructure Deployment" -ForegroundColor Cyan +Write-Host " Version 2.0" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan + +# Prompt for credentials if not provided +if (-not $VbrUsername) { + $VbrUsername = Read-Host "Enter VBR username" +} + +if (-not $VbrPassword) { + $securePassword = Read-Host "Enter password for $VbrUsername" -AsSecureString + $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword) + $VbrPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) +} + +Write-Host "Using credentials for: $VbrUsername" -ForegroundColor Gray + +# ========================================== +# HELPER FUNCTIONS +# ========================================== +function Write-Step { + param([string]$Message, [int]$Step, [int]$Total) + Write-Host "`n[$Step/$Total] $Message" -ForegroundColor Cyan +} + +function Write-Success { + param([string]$Message) + Write-Host "✓ $Message" -ForegroundColor Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "⚠ $Message" -ForegroundColor Yellow +} + +function Write-Skip { + param([string]$Message) + Write-Host "⊘ $Message" -ForegroundColor Yellow +} + +function Wait-ForHostReady { + param( + [Parameter(Mandatory=$true)] + [string]$HostIP, + + [int]$MaxWaitSeconds = 60, + [int]$PollInterval = 5 + ) + + Write-Verbose "Waiting for host $HostIP to be ready..." + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + + while ($stopwatch.Elapsed.TotalSeconds -lt $MaxWaitSeconds) { + try { + $VBRhost = Get-VBRServer -Name $HostIP -ErrorAction Stop + if ($VBRhost) { + Write-Verbose "Host $HostIP is ready" + $stopwatch.Stop() + return $true + } + } catch { + Write-Verbose "Host not ready yet, waiting..." + } + Start-Sleep -Seconds $PollInterval + } + + $stopwatch.Stop() + Write-Warning "Timeout waiting for host $HostIP to be ready" + return $false +} + +function Add-JeOSLinuxHost { + param( + [Parameter(Mandatory=$true)] + [string]$IP, + + [Parameter(Mandatory=$true)] + [string]$PairingCode, + + [Parameter(Mandatory=$true)] + [string]$Description + ) + + $existing = Get-VBRServer | Where-Object { $_.Name -eq $IP } + if ($existing) { + Write-Skip "Linux host $IP already exists" + return $existing + } + + Write-Verbose "Adding Linux host: $IP" + try { + return Add-VBRLinux -Name $IP -UseCertificate -HandshakeCode $PairingCode ` + -ForceDeployerFingerprint -Description $Description -ErrorAction Stop + } catch { + Write-Error "Failed to add Linux host $IP : $_" + throw + } +} + +function Disconnect-VBRServerSafely { + try { + if (Get-VBRServerSession -ErrorAction SilentlyContinue) { + Disconnect-VBRServer -ErrorAction SilentlyContinue + Write-Verbose "Disconnected from VBR server" + } + } catch { + Write-Verbose "Error during disconnect: $_" + } +} + +# ========================================== +# STEP 1: LOAD VEEAM POWERSHELL MODULE +# ========================================== +try { + Write-Step -Message "Loading Veeam PowerShell module..." -Step 1 -Total 7 + + $VBRPSFolder = "C:\Program Files\Veeam\Backup and Replication\Console\Veeam.Backup.PowerShell" + + if (-not (Test-Path $VBRPSFolder)) { + throw "Veeam PowerShell folder not found at: $VBRPSFolder" + } + + # Check if module is already loaded + if (Get-Module -Name Veeam.Backup.PowerShell -ErrorAction SilentlyContinue) { + Write-Skip "Veeam module already loaded" + } else { + Write-Verbose "Loading Veeam assemblies..." + $assemblies = @( + "Veeam.Backup.Core.dll", + "Veeam.Backup.Core.Common.dll", + "Veeam.Backup.Common.dll", + "Veeam.Backup.Model.dll", + "Rebex.Networking.dll", + "Veeam.Backup.AzureAPI.dll" + ) + + foreach ($assembly in $assemblies) { + $path = Join-Path (Split-Path $VBRPSFolder -Parent) $assembly + if (Test-Path $path) { + Add-Type -Path $path -ErrorAction Stop + } else { + Write-Warning "Assembly not found: $assembly (continuing anyway)" + } + } + + Import-Module (Join-Path (Split-Path $VBRPSFolder -Parent) "Veeam.Backup.PowerShell.dll") ` + -DisableNameChecking -ErrorAction Stop + + Write-Success "Veeam PowerShell module loaded successfully" + } +} catch { + Write-Host "✗ Failed to load Veeam PowerShell module: $_" -ForegroundColor Red + Stop-Transcript + exit 1 +} + +# ========================================== +# STEP 2: CONNECT TO VBR SERVER +# ========================================== +try { + Write-Step -Message "Connecting to VBR server..." -Step 2 -Total 7 + + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + $connected = $false + + while ($stopwatch.Elapsed.TotalSeconds -lt $Timeout) { + try { + Disconnect-VBRServerSafely + + Connect-VBRServer -Server $VbrServer -User $VbrUsername -Password $VbrPassword ` + -ForceAcceptTlsCertificate -ErrorAction Stop + + Write-Success "Connected to VBR server: $VbrServer" + $connected = $true + break + } catch { + $elapsedMinutes = [math]::Round($stopwatch.Elapsed.TotalMinutes, 1) + $remainingMinutes = [math]::Round(($Timeout - $stopwatch.Elapsed.TotalSeconds) / 60, 1) + + Write-Warning "Connection attempt failed after $elapsedMinutes minutes: $_" + + if ($stopwatch.Elapsed.TotalSeconds -lt $Timeout) { + Write-Host "⟳ Retrying in $($RetryInterval / 60) minutes... ($remainingMinutes minutes remaining)" -ForegroundColor Cyan + Start-Sleep -Seconds $RetryInterval + } + } + } + + $stopwatch.Stop() + + if (-not $connected) { + throw "Failed to connect to VBR server after $($Timeout/60) minutes" + } +} catch { + Write-Host "✗ $_" -ForegroundColor Red + Stop-Transcript + exit 1 +} + +# ========================================== +# STEP 3: INSTALL LICENSE +# ========================================== +Write-Step -Message "Installing license..." -Step 3 -Total 7 + +if (-not $LicenseFilePath) { + Write-Skip "No license file specified, skipping" +} elseif (-not (Test-Path $LicenseFilePath)) { + Write-Warning "License file not found: $LicenseFilePath" + Write-Host " Continuing without license installation..." -ForegroundColor Yellow +} else { + # Show file info + $licFile = Get-Item $LicenseFilePath + Write-Host " Checking license file: $LicenseFilePath" -ForegroundColor Gray + Write-Host " File size: $($licFile.Length) bytes" -ForegroundColor Gray + Write-Host " Last modified: $($licFile.LastWriteTime)" -ForegroundColor Gray + + # Validate file content + try { + $licContent = Get-Content $LicenseFilePath -Raw -ErrorAction Stop + if ($licContent -match '> "$LOG_FILE" + + case "$level" in + INFO) + echo -e "[INFO] ${message}" + ;; + WARN) + echo -e "[WARN] ${message}" + ;; + ERROR) + echo -e "[ERROR] ${message}" + ;; + *) + echo "[${level}] ${message}" + ;; + esac +} + +#============================================================================== +# Secure cleanup function +#============================================================================== +cleanup() { + log "INFO" "Cleaning up temporary files" + + if [ -f "$COOKIE_JAR" ]; then + shred -u -n 3 "$COOKIE_JAR" 2>/dev/null || rm -f "$COOKIE_JAR" + log "INFO" "Cookie jar deleted" + fi + + unset VSA_PASSWORD TOTP_SECRET TOTP_CODE CSRF_TOKEN + + +} + +trap cleanup EXIT + +#============================================================================== +# Preliminary checks +#============================================================================== +log "INFO" "Starting VCSP external managers installation automation" + +if [ -z "$TOTP_SECRET" ] || [ -z "$VSA_USER" ] || [ -z "$VSA_PASSWORD" ]; then + log "ERROR" "Usage: $0 " + exit 1 +fi + +if ! command -v oathtool &> /dev/null; then + log "ERROR" "oathtool not found - Installation: dnf install oathtool" + exit 1 +fi + +if ! command -v curl &> /dev/null; then + log "ERROR" "curl not found" + exit 1 +fi + +#============================================================================== +# Retrieve local IP address +#============================================================================== +log "INFO" "Retrieving local IP address" + +VSA_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + VSA_IP=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K[^ ]+') +fi + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + VSA_IP=$(ifconfig 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | head -n1) +fi + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + log "ERROR" "Unable to retrieve local IP address" + exit 1 +fi + +VSA_URL="https://${VSA_IP}:${VSA_PORT}" +log "INFO" "VSA URL: ${VSA_URL}" + +#============================================================================== +# Generate TOTP code +#============================================================================== +log "INFO" "Generating TOTP code" +TOTP_CODE=$(oathtool --totp -b "$TOTP_SECRET" 2>/dev/null) + +if [ -z "$TOTP_CODE" ]; then + log "ERROR" "TOTP generation failed" + exit 1 +fi + +log "INFO" "TOTP code generated: ${TOTP_CODE}" +TIMESTAMP=$(date +%s) + +#============================================================================== +# Step 1: Initial authentication attempt (expecting HTTP 428 - MFA required) +#============================================================================== +log "INFO" "Step 1/3: Initial authentication" + +RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/auth/login" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Accept: */*" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"user\":\"${VSA_USER}\",\"password\":\"${VSA_PASSWORD}\"}") + +HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + +if [ "$HTTP_CODE" = "428" ]; then + log "INFO" "MFA required (HTTP 428) - proceeding with OTP" +elif [ "$HTTP_CODE" = "200" ]; then + log "INFO" "Authentication successful without MFA" +else + log "ERROR" "Unexpected response during authentication (HTTP ${HTTP_CODE})" + exit 1 +fi + +sleep 1 + +#============================================================================== +# Step 2: Authentication with MFA +#============================================================================== +log "INFO" "Step 2/3: Authentication with MFA code" + +RESPONSE=$(curl -k -s -i \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/auth/login" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "X-OTP-TOKEN: ${TOTP_CODE}" \ + -H "Accept: */*" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"user\":\"${VSA_USER}\",\"password\":\"${VSA_PASSWORD}\"}" 2>&1) + +# Extract CSRF token from response headers +CSRF_TOKEN=$(echo "$RESPONSE" | grep -i "X-CSRF-TOKEN:" | awk '{print $2}' | tr -d '\r') + +if [ -z "$CSRF_TOKEN" ]; then + log "ERROR" "Authentication with MFA failed - no CSRF token received" + exit 1 +fi + +log "INFO" "Authentication successful - CSRF token: ${CSRF_TOKEN}" +sleep 1 + +#============================================================================== +# Step 3: Request external_managers_installation +#============================================================================== +log "INFO" "Step 3/3: Requesting external_managers_installation" + +RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" \ + -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/v1/integration/external_managers_installation?" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Accept: application/json" \ + -H "X-CSRF-TOKEN: ${CSRF_TOKEN}" \ + -H "Origin: ${VSA_URL}" \ + -H "Referer: ${VSA_URL}/" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d '{}') + +HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +BODY=$(echo "$RESPONSE" | sed 's/HTTP_CODE:.*//') + +if [ "$HTTP_CODE" = "200" ]; then + log "INFO" "External managers installation requested successfully" + log "INFO" "Response: ${BODY}" +else + log "ERROR" "Failed to request external managers installation (HTTP ${HTTP_CODE})" + log "ERROR" "Response: ${BODY}" + exit 1 +fi + +log "INFO" "Process completed successfully" +#log "INFO" "Cleanup in progress" + +exit 0 + diff --git a/VSA-Autodeploy/vcsp/veeam_sovalidrequest.sh b/VSA-Autodeploy/vcsp/veeam_sovalidrequest.sh new file mode 100644 index 0000000..8710989 --- /dev/null +++ b/VSA-Autodeploy/vcsp/veeam_sovalidrequest.sh @@ -0,0 +1,284 @@ +#!/bin/bash + +#============================================================================== +# VCSP Automation Script - SecOF Request Auto-Validation +# Auto-destruction after execution +#============================================================================== + +# Configuration +VSA_USER="$2" +VSA_PASSWORD="$3" +TOTP_SECRET="$1" +VSA_PORT="10443" + +# Temporary files +COOKIE_JAR="/tmp/vcsp_session_$$_$(date +%s)" +LOG_FILE="/var/log/vcsp_so_valid_request.log" +SCRIPT_PATH="$0" + +#============================================================================== +# Secure logging function +#============================================================================== +log() { + local level="$1" + shift + local message="$*" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[${timestamp}] [${level}] ${message}" >> "$LOG_FILE" + + case "$level" in + INFO) + echo -e "[INFO] ${message}" + ;; + WARN) + echo -e "[WARN] ${message}" + ;; + ERROR) + echo -e "[ERROR] ${message}" + ;; + *) + echo "[${level}] ${message}" + ;; + esac +} + +#============================================================================== +# Secure cleanup function +#============================================================================== +cleanup() { + log "INFO" "Cleaning up temporary files" + + if [ -f "$COOKIE_JAR" ]; then + shred -u -n 3 "$COOKIE_JAR" 2>/dev/null || rm -f "$COOKIE_JAR" + log "INFO" "Cookie jar deleted" + fi + + unset VSA_PASSWORD TOTP_SECRET TOTP_CODE CSRF_TOKEN +} + +trap cleanup EXIT + +#============================================================================== +# Preliminary checks +#============================================================================== +log "INFO" "Starting VCSP SecOF request auto-validation" + +if [ -z "$TOTP_SECRET" ] || [ -z "$VSA_USER" ] || [ -z "$VSA_PASSWORD" ]; then + log "ERROR" "Usage: $0 " + exit 1 +fi + +if ! command -v oathtool &> /dev/null; then + log "ERROR" "oathtool not found - Installation: dnf install oathtool" + exit 1 +fi + +if ! command -v curl &> /dev/null; then + log "ERROR" "curl not found" + exit 1 +fi + +if ! command -v jq &> /dev/null; then + log "WARN" "jq not found - will use grep fallback for JSON parsing" +fi + +#============================================================================== +# Retrieve local IP address +#============================================================================== +log "INFO" "Retrieving local IP address" + +VSA_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + VSA_IP=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K[^ ]+') +fi + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + VSA_IP=$(ifconfig 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | head -n1) +fi + +if [ -z "$VSA_IP" ] || [ "$VSA_IP" = "127.0.0.1" ]; then + log "ERROR" "Unable to retrieve local IP address" + exit 1 +fi + +VSA_URL="https://${VSA_IP}:${VSA_PORT}" +log "INFO" "VSA URL: ${VSA_URL}" + +#============================================================================== +# Generate TOTP code +#============================================================================== +log "INFO" "Generating TOTP code" +TOTP_CODE=$(oathtool --totp -b "$TOTP_SECRET" 2>/dev/null) + +if [ -z "$TOTP_CODE" ]; then + log "ERROR" "TOTP generation failed" + exit 1 +fi + +log "INFO" "TOTP code generated: ${TOTP_CODE}" +TIMESTAMP=$(date +%s) + +#============================================================================== +# Step 1: Initial authentication attempt (expecting HTTP 428 - MFA required) +#============================================================================== +log "INFO" "Step 1/4: Initial authentication" + +RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/auth/login" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Accept: */*" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"user\":\"${VSA_USER}\",\"password\":\"${VSA_PASSWORD}\"}") + +HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + +if [ "$HTTP_CODE" = "428" ]; then + log "INFO" "MFA required (HTTP 428) - proceeding with OTP" +elif [ "$HTTP_CODE" = "200" ]; then + log "INFO" "Authentication successful without MFA" +else + log "ERROR" "Unexpected response during authentication (HTTP ${HTTP_CODE})" + exit 1 +fi + +sleep 1 + +#============================================================================== +# Step 2: Authentication with MFA +#============================================================================== +log "INFO" "Step 2/4: Authentication with MFA code" + +RESPONSE=$(curl -k -s -i \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/auth/login" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "X-OTP-TOKEN: ${TOTP_CODE}" \ + -H "Accept: */*" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d "{\"user\":\"${VSA_USER}\",\"password\":\"${VSA_PASSWORD}\"}" 2>&1) + +# Extract CSRF token from response headers +CSRF_TOKEN=$(echo "$RESPONSE" | grep -i "X-CSRF-TOKEN:" | awk '{print $2}' | tr -d '\r') + +if [ -z "$CSRF_TOKEN" ]; then + log "ERROR" "Authentication with MFA failed - no CSRF token received" + exit 1 +fi + +log "INFO" "Authentication successful - CSRF token: ${CSRF_TOKEN}" +sleep 1 + +#============================================================================== +# Step 3: Get pending SecOF requests +#============================================================================== +log "INFO" "Step 3/4: Retrieving pending SecOF requests" + +RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" \ + -b "$COOKIE_JAR" \ + -X GET "${VSA_URL}/api/v1/secof/requests" \ + -H "Accept: application/json" \ + -H "X-CSRF-TOKEN: ${CSRF_TOKEN}" \ + -H "Referer: ${VSA_URL}/overview" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36") + +HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) +BODY=$(echo "$RESPONSE" | sed 's/HTTP_CODE:.*//') + +if [ "$HTTP_CODE" != "200" ]; then + log "ERROR" "Failed to retrieve SecOF requests (HTTP ${HTTP_CODE})" + exit 1 +fi + +log "INFO" "SecOF requests retrieved successfully" + +# Parse JSON to extract request IDs +if command -v jq &> /dev/null; then + # Use jq if available (preferred method) + REQUEST_IDS=($(echo "$BODY" | jq -r '.[].id' 2>/dev/null)) + REQUEST_TYPES=($(echo "$BODY" | jq -r '.[].type' 2>/dev/null)) + REQUEST_DESCRIPTIONS=($(echo "$BODY" | jq -r '.[].description' 2>/dev/null)) +else + # Fallback to grep if jq is not available + REQUEST_IDS=($(echo "$BODY" | grep -oP '"id":"\K[^"]+')) + REQUEST_TYPES=($(echo "$BODY" | grep -oP '"type":"\K[^"]+')) +fi + +if [ ${#REQUEST_IDS[@]} -eq 0 ]; then + log "WARN" "No pending SecOF requests found" + log "INFO" "Nothing to validate - exiting" + exit 0 +fi + +log "INFO" "Found ${#REQUEST_IDS[@]} pending request(s)" + +#============================================================================== +# Step 4: Validate each pending SecOF request +#============================================================================== +log "INFO" "Step 4/4: Validating SecOF requests" + +SUCCESS_COUNT=0 +FAIL_COUNT=0 + +for i in "${!REQUEST_IDS[@]}"; do + REQUEST_ID="${REQUEST_IDS[$i]}" + REQUEST_TYPE="${REQUEST_TYPES[$i]:-Unknown}" + + log "INFO" "Validating request [$((i+1))/${#REQUEST_IDS[@]}]: ${REQUEST_ID} (Type: ${REQUEST_TYPE})" + + RESPONSE=$(curl -k -s -w "\nHTTP_CODE:%{http_code}" \ + -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ + -X POST "${VSA_URL}/api/v1/secof/requests/${REQUEST_ID}?" \ + -H "Content-Type: application/json;charset=UTF-8" \ + -H "Accept: application/json" \ + -H "X-CSRF-TOKEN: ${CSRF_TOKEN}" \ + -H "Origin: ${VSA_URL}" \ + -H "Referer: ${VSA_URL}/" \ + -H "Connection: keep-alive" \ + -H "User-Agent: Mozilla/5.0 (Linux) AppleWebKit/537.36" \ + -d '{}') + + HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) + BODY=$(echo "$RESPONSE" | sed 's/HTTP_CODE:.*//') + + if [ "$HTTP_CODE" = "204" ]; then + log "INFO" "Request ${REQUEST_ID} validated successfully (HTTP 204)" + SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) + elif [ "$HTTP_CODE" = "200" ]; then + log "INFO" "Request ${REQUEST_ID} validated successfully (HTTP 200)" + log "INFO" "Response: ${BODY}" + SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) + else + log "ERROR" "Failed to validate request ${REQUEST_ID} (HTTP ${HTTP_CODE})" + log "ERROR" "Response: ${BODY}" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + + # Small delay between requests + sleep 1 +done + +#============================================================================== +# Summary +#============================================================================== +log "INFO" "================================================" +log "INFO" "Validation Summary:" +log "INFO" " Total requests: ${#REQUEST_IDS[@]}" +log "INFO" " Successful: ${SUCCESS_COUNT}" +log "INFO" " Failed: ${FAIL_COUNT}" +log "INFO" "================================================" + +if [ $FAIL_COUNT -gt 0 ]; then + log "WARN" "Some requests failed validation" + exit 1 +fi + +log "INFO" "All requests validated successfully" +#log "INFO" "Cleanup in progress" + +exit 0 + diff --git a/VSA-Autodeploy/vhr.json b/VSA-Autodeploy/vhr.json new file mode 100644 index 0000000..27f3feb --- /dev/null +++ b/VSA-Autodeploy/vhr.json @@ -0,0 +1,30 @@ +{ + "SourceISO": "VeeamInfrastructureAppliance_13.0.1.180_20251101.iso", + "OutputISO": "VHR_13.0.1.180_20251101.iso", + "ApplianceType": "VIAHR", + "InPlace": false, + "CreateBackup": true, + "CleanupCFGFiles": false, + "CFGOnly": false, + "GrubTimeout": 0, + "KeyboardLayout": "fr", + "Timezone": "Europe/Paris", + "Hostname": "VHR", + "UseDHCP": true, + "StaticIP": "192.168.1.147", + "Subnet": "255.255.255.0", + "Gateway": "192.168.1.1", + "DNSServers": ["192.168.1.64", "8.8.8.4", "8.8.8.8"], + "VeeamAdminPassword": "123q123Q123!123", + "VeeamAdminMfaSecretKey": "JBSWY2DPEHPK2PXP", + "VeeamAdminIsMfaEnabled": "true", + "VeeamSoPassword": "123w123W123!123", + "VeeamSoMfaSecretKey": "JBSWY2DPEHPK2PXP", + "VeeamSoIsMfaEnabled": "true", + "VeeamSoRecoveryToken": "12345678-90ab-cdef-1234-567890abcdef", + "VeeamSoIsEnabled": "true", + "NtpServer": "time.nist.gov", + "NtpRunSync": "true", + "NodeExporter": false, + "Debug": false +} diff --git a/VSA-Autodeploy/viaproxy.json b/VSA-Autodeploy/viaproxy.json new file mode 100644 index 0000000..2e0863c --- /dev/null +++ b/VSA-Autodeploy/viaproxy.json @@ -0,0 +1,30 @@ +{ + "SourceISO": "VeeamInfrastructureAppliance_13.0.1.180_20251101.iso", + "OutputISO": "VIA_13.0.1.180_20251101.iso", + "ApplianceType": "VIA", + "InPlace": false, + "CreateBackup": true, + "CleanupCFGFiles": false, + "CFGOnly": false, + "GrubTimeout": 0, + "KeyboardLayout": "fr", + "Timezone": "Europe/Paris", + "Hostname": "VIAPROXY", + "UseDHCP": true, + "StaticIP": "192.168.1.147", + "Subnet": "255.255.255.0", + "Gateway": "192.168.1.1", + "DNSServers": ["192.168.1.64", "8.8.8.4", "8.8.8.8"], + "VeeamAdminPassword": "123q123Q123!123", + "VeeamAdminMfaSecretKey": "JBSWY2DPEHPK2PXP", + "VeeamAdminIsMfaEnabled": "true", + "VeeamSoPassword": "123w123W123!123", + "VeeamSoMfaSecretKey": "JBSWY2DPEHPK2PXP", + "VeeamSoIsMfaEnabled": "true", + "VeeamSoRecoveryToken": "12345678-90ab-cdef-1234-567890abcdef", + "VeeamSoIsEnabled": "true", + "NtpServer": "time.nist.gov", + "NtpRunSync": "true", + "NodeExporter": false, + "Debug": false +}