diff --git a/assets/win11x64-enterprise-eval.xml b/assets/win11x64-enterprise-eval.xml
index 7822f82..c41d244 100644
--- a/assets/win11x64-enterprise-eval.xml
+++ b/assets/win11x64-enterprise-eval.xml
@@ -9,6 +9,7 @@
en-US
en-US
en-US
+ UTC
@@ -137,6 +138,7 @@
https://github.com/dockur/windows/issues
Windows for Docker
+ UTC
1
@@ -301,6 +303,7 @@
1
+ UTC
diff --git a/assets/win11x64-enterprise.xml b/assets/win11x64-enterprise.xml
index 6620eb7..c5153a4 100644
--- a/assets/win11x64-enterprise.xml
+++ b/assets/win11x64-enterprise.xml
@@ -9,6 +9,7 @@
en-US
en-US
en-US
+ UTC
@@ -140,6 +141,7 @@
https://github.com/dockur/windows/issues
Windows for Docker
+ UTC
1
@@ -304,6 +306,7 @@
1
+ UTC
diff --git a/assets/win11x64-iot.xml b/assets/win11x64-iot.xml
index 4707774..03eabb1 100644
--- a/assets/win11x64-iot.xml
+++ b/assets/win11x64-iot.xml
@@ -9,6 +9,7 @@
en-US
en-US
en-US
+ UTC
@@ -140,6 +141,7 @@
https://github.com/dockur/windows/issues
Windows for Docker
+ UTC
1
@@ -304,6 +306,7 @@
1
+ UTC
diff --git a/assets/win11x64-ltsc.xml b/assets/win11x64-ltsc.xml
index 4359b1c..eb63d15 100644
--- a/assets/win11x64-ltsc.xml
+++ b/assets/win11x64-ltsc.xml
@@ -9,6 +9,7 @@
en-US
en-US
en-US
+ UTC
@@ -140,6 +141,7 @@
https://github.com/dockur/windows/issues
Windows for Docker
+ UTC
1
@@ -304,6 +306,7 @@
1
+ UTC
diff --git a/assets/win11x64.xml b/assets/win11x64.xml
index bec96d4..b8f58aa 100644
--- a/assets/win11x64.xml
+++ b/assets/win11x64.xml
@@ -9,6 +9,7 @@
en-US
en-US
en-US
+ UTC
@@ -140,6 +141,7 @@
https://github.com/dockur/windows/issues
Windows for Docker
+ UTC
1
@@ -304,6 +306,7 @@
1
+ UTC
diff --git a/src/boot.sh b/src/boot.sh
new file mode 100755
index 0000000..63e9dec
--- /dev/null
+++ b/src/boot.sh
@@ -0,0 +1,161 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+# Docker environment variables
+: "${BIOS:=""}" # BIOS file
+: "${TPM:="N"}" # Disable TPM
+: "${SMM:="N"}" # Disable SMM
+
+BOOT_DESC=""
+BOOT_OPTS=""
+
+SECURE="off"
+[[ "$SMM" == [Yy1]* ]] && SECURE="on"
+[ -n "$BIOS" ] && BOOT_MODE="custom"
+
+case "${BOOT_MODE,,}" in
+ "uefi" | "" )
+ BOOT_MODE="uefi"
+ ROM="OVMF_CODE_4M.fd"
+ VARS="OVMF_VARS_4M.fd"
+ ;;
+ "secure" )
+ SECURE="on"
+ BOOT_DESC=" securely"
+ ROM="OVMF_CODE_4M.secboot.fd"
+ VARS="OVMF_VARS_4M.secboot.fd"
+ ;;
+ "windows" | "windows_plain" )
+ ROM="OVMF_CODE_4M.fd"
+ VARS="OVMF_VARS_4M.fd"
+ ;;
+ "windows_secure" )
+ TPM="Y"
+ SECURE="on"
+ BOOT_DESC=" securely"
+ ROM="OVMF_CODE_4M.ms.fd"
+ VARS="OVMF_VARS_4M.ms.fd"
+ ;;
+ "windows_legacy" )
+ HV="N"
+ SECURE="on"
+ BOOT_DESC=" (legacy)"
+ [ -z "${USB:-}" ] && USB="usb-ehci,id=ehci"
+ ;;
+ "legacy" )
+ BOOT_DESC=" with SeaBIOS"
+ ;;
+ "custom" )
+ BOOT_OPTS="-bios $BIOS"
+ BOOT_DESC=" with custom BIOS file"
+ ;;
+ *)
+ error "Unknown BOOT_MODE, value \"${BOOT_MODE}\" is not recognized!"
+ exit 33
+ ;;
+esac
+
+if [[ "${BOOT_MODE,,}" == "windows"* ]]; then
+ BOOT_OPTS+=" -rtc base=utc"
+ BOOT_OPTS+=" -global ICH9-LPC.disable_s3=1"
+ BOOT_OPTS+=" -global ICH9-LPC.disable_s4=1"
+fi
+
+case "${BOOT_MODE,,}" in
+ "uefi" | "secure" | "windows" | "windows_plain" | "windows_secure" )
+
+ OVMF="/usr/share/OVMF"
+ DEST="$STORAGE/${BOOT_MODE,,}"
+
+ if [ ! -s "$DEST.rom" ] || [ ! -f "$DEST.rom" ]; then
+ [ ! -s "$OVMF/$ROM" ] || [ ! -f "$OVMF/$ROM" ] && error "UEFI boot file ($OVMF/$ROM) not found!" && exit 44
+ cp "$OVMF/$ROM" "$DEST.rom"
+ fi
+
+ if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then
+ [ ! -s "$OVMF/$VARS" ] || [ ! -f "$OVMF/$VARS" ]&& error "UEFI vars file ($OVMF/$VARS) not found!" && exit 45
+ cp "$OVMF/$VARS" "$DEST.vars"
+ fi
+
+ if [[ "${BOOT_MODE,,}" == "secure" ]] || [[ "${BOOT_MODE,,}" == "windows_secure" ]]; then
+ BOOT_OPTS+=" -global driver=cfi.pflash01,property=secure,value=on"
+ fi
+
+ BOOT_OPTS+=" -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on"
+ BOOT_OPTS+=" -drive file=$DEST.vars,if=pflash,unit=1,format=raw"
+
+ ;;
+esac
+
+MSRS="/sys/module/kvm/parameters/ignore_msrs"
+if [ -e "$MSRS" ]; then
+ result=$(<"$MSRS")
+ result="${result//[![:print:]]/}"
+ if [[ "$result" == "0" ]] || [[ "${result^^}" == "N" ]]; then
+ echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
+ fi
+fi
+
+CLOCKSOURCE="tsc"
+[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
+CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
+
+if [ ! -f "$CLOCK" ]; then
+ warn "file \"$CLOCK\" cannot not found?"
+else
+ result=$(<"$CLOCK")
+ result="${result//[![:print:]]/}"
+ case "${result,,}" in
+ "${CLOCKSOURCE,,}" ) ;;
+ "kvm-clock" ) info "Nested KVM virtualization detected.." ;;
+ "hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
+ "hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
+ *) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
+ esac
+fi
+
+SM_BIOS=""
+PS="/sys/class/dmi/id/product_serial"
+
+if [ -s "$PS" ] && [ -r "$PS" ]; then
+
+ BIOS_SERIAL=$(<"$PS")
+ BIOS_SERIAL="${BIOS_SERIAL//[![:alnum:]]/}"
+
+ if [ -n "$BIOS_SERIAL" ]; then
+ SM_BIOS="-smbios type=1,serial=$BIOS_SERIAL"
+ fi
+
+fi
+
+if [[ "$TPM" == [Yy1]* ]]; then
+
+ rm -f /var/run/tpm.pid
+
+ if ! swtpm socket -t -d --tpmstate "backend-uri=file://$STORAGE/${BOOT_MODE,,}.tpm" --ctrl type=unixio,path=/run/swtpm-sock --pid file=/var/run/tpm.pid --tpm2; then
+ error "Failed to start TPM emulator, reason: $?"
+ else
+
+ for (( i = 1; i < 20; i++ )); do
+
+ [ -S "/run/swtpm-sock" ] && break
+
+ if (( i % 10 == 0 )); then
+ echo "Waiting for TPM emulator to become available..."
+ fi
+
+ sleep 0.1
+
+ done
+
+ if [ ! -S "/run/swtpm-sock" ]; then
+ error "TPM socket not found? Disabling TPM module..."
+ else
+ BOOT_OPTS+=" -chardev socket,id=chrtpm,path=/run/swtpm-sock"
+ BOOT_OPTS+=" -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
+ fi
+
+ fi
+fi
+
+return 0
diff --git a/src/define.sh b/src/define.sh
index 69896b1..d10fe5e 100644
--- a/src/define.sh
+++ b/src/define.sh
@@ -1412,7 +1412,7 @@ prepareInstall() {
echo " OEMSkipRegional=1"
echo " OemSkipWelcome=1"
echo " AdminPassword=$password"
- echo " TimeZone=0"
+ echo " TimeZone=85"
echo " AutoLogon=Yes"
echo " AutoLogonCount=65432"
echo ""
diff --git a/src/qga.py b/src/qga.py
index c57f017..64b818e 100644
--- a/src/qga.py
+++ b/src/qga.py
@@ -29,58 +29,61 @@ def decode_output(data):
return ""
try:
- # Try Hex decoding first
return bytes.fromhex(data).decode("utf-8", errors="ignore")
except ValueError:
pass
try:
- # If hex fails, try Base64 decoding
return base64.b64decode(data).decode("utf-8", errors="ignore")
except ValueError:
pass
- # If all decoding fails, return raw
return data
-def execute_command(sock, command_path, command_args):
+def execute_command(sock, command_path, command_args, timeout):
"""Execute a command inside the guest VM with specified path and arguments."""
exec_request = {
"execute": "guest-exec",
"arguments": {
"path": command_path,
"arg": command_args,
- "capture-output": True, # Capture stdout and stderr
+ "capture-output": True,
},
}
+
+ print(f"Executing: {command_path} {' '.join(command_args)}")
response = send_qga_command(sock, exec_request)
- if response is None:
- return None
-
- if "return" not in response or "pid" not in response["return"]:
- print("Error: Failed to start execution:", response, file=sys.stderr)
+ if response is None or "return" not in response or "pid" not in response["return"]:
+ print(
+ "Error: Failed to start execution.",
+ json.dumps(response or {}, indent=2),
+ file=sys.stderr,
+ )
return None
pid = response["return"]["pid"]
print(f"Command started with PID {pid}")
- # Step 2: Wait for completion
+ # Step 2: Wait for completion with timeout
+ start_time = time.time()
+ status = {}
while True:
+ if time.time() - start_time > timeout:
+ print("Execution timeout reached.", file=sys.stderr)
+ return {"exit_code": -2, "stdout": "", "stderr": "Execution timed out."}
+
status_request = {"execute": "guest-exec-status", "arguments": {"pid": pid}}
status_response = send_qga_command(sock, status_request)
- if status_response is None:
- continue
-
- if "return" in status_response:
+ if status_response and "return" in status_response:
status = status_response["return"]
if status.get("exited", False):
- break # Command finished
- time.sleep(0.2) # Wait before checking again
+ break
+
+ time.sleep(0.2)
- # Step 3: Get exit code and output
exit_code = status.get("exitcode", -1)
stdout_data = decode_output(status.get("out-data", ""))
stderr_data = decode_output(status.get("err-data", ""))
@@ -91,7 +94,7 @@ def execute_command(sock, command_path, command_args):
def create_socket():
"""Create and return a reusable socket connection to the QEMU Guest Agent."""
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.settimeout(30) # 30 seconds timeout
+ sock.settimeout(30)
try:
sock.connect(QGA_SOCKET)
return sock
@@ -103,6 +106,21 @@ def create_socket():
def parse_args():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description="Send commands to QEMU Guest Agent.")
+ shell_group = parser.add_mutually_exclusive_group()
+ shell_group.add_argument(
+ "--cmd", action="store_true", help="Run the command through cmd.exe /c"
+ )
+ shell_group.add_argument(
+ "--powershell",
+ action="store_true",
+ help="Run the command with powershell -Command",
+ )
+ parser.add_argument(
+ "--timeout", type=int, default=60, help="Max execution time in seconds"
+ )
+ parser.add_argument(
+ "--json", action="store_true", help="Output result in JSON format"
+ )
parser.add_argument(
"command", help="Path to the command to execute inside the guest VM"
)
@@ -113,23 +131,35 @@ def parse_args():
if __name__ == "__main__":
- # Parse command-line arguments
args = parse_args()
- # Create a reusable socket
+ if args.cmd:
+ command_path = "cmd.exe"
+ command_args = ["/c", args.command] + args.args
+ elif args.powershell:
+ command_path = "powershell.exe"
+ full_command = " ".join([args.command] + args.args)
+ command_args = ["-Command", full_command]
+ else:
+ command_path = args.command
+ command_args = args.args
+
+ # Create a reusable socket
unix_sock = create_socket()
if not unix_sock:
print("Failed to create socket.", file=sys.stderr)
sys.exit(1) # Exit if we can't connect to the socket
- # Execute the command
- result = execute_command(unix_sock, args.command, args.args)
+ result = execute_command(unix_sock, command_path, command_args, args.timeout)
if result:
- print(f"Exit Code: {result['exit_code']}")
- if result["stdout"]:
- print("STDOUT:\n", result["stdout"])
- if result["stderr"]:
- print("STDERR:\n", result["stderr"])
+ if args.json:
+ print(json.dumps(result, indent=2))
+ else:
+ print(f"Exit Code: {result['exit_code']}")
+ if result["stdout"]:
+ print("STDOUT:\n", result["stdout"])
+ if result["stderr"]:
+ print("STDERR:\n", result["stderr"])
# Close the socket once all commands are executed
unix_sock.close()