feat(core): rewrite installer with modular config, dynamic drive resolver, and clean orchestration
This commit is contained in:
167
main.py
167
main.py
@@ -1,27 +1,148 @@
|
|||||||
import os
|
"""
|
||||||
|
Main Orchestrator for Acc Installer.
|
||||||
|
Executes directory setup, file copy, archive extraction, and automated installation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
from module.config import InstallerConfig, detect_target_drive
|
||||||
MODULE_DIR = os.path.join(BASE_DIR, "module")
|
from module.downloader_copier import setup_directories_and_copy
|
||||||
|
from module.extract_acc import extract_acc_archive
|
||||||
|
from module.install_novindesk import install_novindesk
|
||||||
|
|
||||||
scripts = [
|
|
||||||
"downloader_copier.py",
|
|
||||||
"extract_acc.py",
|
|
||||||
"install_novindesk.py"
|
|
||||||
]
|
|
||||||
|
|
||||||
for script in scripts:
|
def setup_logger(verbose: bool = False) -> logging.Logger:
|
||||||
script_path = os.path.join(MODULE_DIR, script)
|
level = logging.DEBUG if verbose else logging.INFO
|
||||||
print(f"[+] Running {script}...")
|
logging.basicConfig(
|
||||||
result = subprocess.run([sys.executable, script_path], capture_output=True, text=True)
|
level=level,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
if result.returncode == 0:
|
datefmt="%H:%M:%S"
|
||||||
print(f"[+] {script} executed successfully.\n")
|
)
|
||||||
else:
|
return logging.getLogger("acc_installer")
|
||||||
print(f"[-] Error running {script}:")
|
|
||||||
if result.stdout:
|
|
||||||
print(result.stdout)
|
def parse_arguments() -> argparse.Namespace:
|
||||||
if result.stderr:
|
parser = argparse.ArgumentParser(
|
||||||
print(result.stderr)
|
description="NovinPardaz Acc Installer - Automated Setup Pipeline",
|
||||||
break
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--source",
|
||||||
|
type=str,
|
||||||
|
default=r"C:\SoftwareNP",
|
||||||
|
help="Source directory containing installation archives"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--drive",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Target drive for installation (e.g. 'D:\\'). Auto-detected if not specified."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Simulate execution without modifying files or launching processes"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--skip-acc",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip extraction of NovinAcc archive"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--skip-desk",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip installation of NovinDesk"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-ui",
|
||||||
|
action="store_true",
|
||||||
|
help="Disable automatic UI interaction during NovinDesk installation"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose",
|
||||||
|
"-v",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable detailed debug logging"
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def run_pipeline(config: InstallerConfig,
|
||||||
|
skip_acc: bool = False,
|
||||||
|
skip_desk: bool = False,
|
||||||
|
auto_ui: bool = True) -> bool:
|
||||||
|
"""
|
||||||
|
Executes the installer pipeline steps sequentially.
|
||||||
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
print("=" * 60)
|
||||||
|
print(" NovinPardaz Acc Installer Pipeline ")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f" Source Directory : {config.source_dir}")
|
||||||
|
print(f" Target Drive : {config.target_drive}")
|
||||||
|
print(f" Target Base Dir : {config.base_dir}")
|
||||||
|
print(f" Dry Run Mode : {config.dry_run}")
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Directory Setup & Software Copy
|
||||||
|
print("\n[Step 1/3] Setting up directories and copying software files...")
|
||||||
|
setup_directories_and_copy(config)
|
||||||
|
print("[+] Step 1 completed successfully.")
|
||||||
|
|
||||||
|
# Step 2: Extract NovinAcc Archive
|
||||||
|
if not skip_acc:
|
||||||
|
print("\n[Step 2/3] Extracting NovinAcc accounting archive...")
|
||||||
|
extract_acc_archive(config)
|
||||||
|
print("[+] Step 2 completed successfully.")
|
||||||
|
else:
|
||||||
|
print("\n[Step 2/3] Skipped NovinAcc extraction (--skip-acc specified).")
|
||||||
|
|
||||||
|
# Step 3: Install NovinDesk
|
||||||
|
if not skip_desk:
|
||||||
|
print("\n[Step 3/3] Extracting and launching NovinDesk installer...")
|
||||||
|
install_novindesk(config, auto_ui=auto_ui)
|
||||||
|
print("[+] Step 3 completed successfully.")
|
||||||
|
else:
|
||||||
|
print("\n[Step 3/3] Skipped NovinDesk installation (--skip-desk specified).")
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print(f"[SUCCESS] All installation steps completed in {elapsed:.2f} seconds.")
|
||||||
|
print("=" * 60)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
print(f"\n[-] Installation pipeline failed after {elapsed:.2f} seconds!")
|
||||||
|
print(f"[-] Error: {exc}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_arguments()
|
||||||
|
setup_logger(args.verbose)
|
||||||
|
|
||||||
|
target_drive = args.drive if args.drive else detect_target_drive()
|
||||||
|
config = InstallerConfig(
|
||||||
|
source_dir=args.source,
|
||||||
|
target_drive=target_drive,
|
||||||
|
dry_run=args.dry_run
|
||||||
|
)
|
||||||
|
|
||||||
|
success = run_pipeline(
|
||||||
|
config=config,
|
||||||
|
skip_acc=args.skip_acc,
|
||||||
|
skip_desk=args.skip_desk,
|
||||||
|
auto_ui=not args.no_ui
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@@ -1 +1,16 @@
|
|||||||
# Module package
|
"""
|
||||||
|
NovinPardaz Acc Installer Package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from module.config import InstallerConfig, detect_target_drive
|
||||||
|
from module.downloader_copier import setup_directories_and_copy
|
||||||
|
from module.extract_acc import extract_acc_archive
|
||||||
|
from module.install_novindesk import install_novindesk
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"InstallerConfig",
|
||||||
|
"detect_target_drive",
|
||||||
|
"setup_directories_and_copy",
|
||||||
|
"extract_acc_archive",
|
||||||
|
"install_novindesk",
|
||||||
|
]
|
||||||
|
|||||||
64
module/config.py
Normal file
64
module/config.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
Configuration and environment resolution for Acc Installer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger("acc_installer")
|
||||||
|
|
||||||
|
|
||||||
|
def detect_target_drive(candidates: Optional[List[str]] = None) -> str:
|
||||||
|
"""
|
||||||
|
Find the first available non-system drive on Windows.
|
||||||
|
Falls back to 'C:\\' if no secondary drive is found.
|
||||||
|
"""
|
||||||
|
if candidates is None:
|
||||||
|
candidates = ["D", "E", "F", "G", "H"]
|
||||||
|
|
||||||
|
for letter in candidates:
|
||||||
|
drive_path = f"{letter}:\\"
|
||||||
|
if os.path.exists(drive_path):
|
||||||
|
logger.info("Found candidate drive: %s", drive_path)
|
||||||
|
return drive_path
|
||||||
|
|
||||||
|
logger.warning("No secondary drive found in candidates. Falling back to C:\\")
|
||||||
|
return "C:\\"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InstallerConfig:
|
||||||
|
"""
|
||||||
|
Centralized configuration holding paths and operational flags for the installer.
|
||||||
|
"""
|
||||||
|
source_dir: Path = field(default_factory=lambda: Path(r"C:\SoftwareNP"))
|
||||||
|
target_drive: str = field(default_factory=detect_target_drive)
|
||||||
|
dry_run: bool = False
|
||||||
|
|
||||||
|
# Calculated paths
|
||||||
|
base_dir: Path = field(init=False)
|
||||||
|
soft_dir: Path = field(init=False)
|
||||||
|
acc_dir: Path = field(init=False)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
# Normalize target drive
|
||||||
|
if not self.target_drive.endswith("\\") and not self.target_drive.endswith("/"):
|
||||||
|
self.target_drive = f"{self.target_drive}\\"
|
||||||
|
|
||||||
|
drive_path = Path(self.target_drive)
|
||||||
|
self.base_dir = drive_path / "NovinPardazOne"
|
||||||
|
self.soft_dir = self.base_dir / "NovinSoft"
|
||||||
|
self.acc_dir = self.base_dir / "NovinAcc"
|
||||||
|
|
||||||
|
def ensure_directories(self) -> None:
|
||||||
|
"""Create necessary target directories if they do not exist."""
|
||||||
|
if self.dry_run:
|
||||||
|
logger.info("[DRY-RUN] Would create: %s and %s", self.soft_dir, self.acc_dir)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.soft_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.acc_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
logger.debug("Ensured directories at %s and %s", self.soft_dir, self.acc_dir)
|
||||||
@@ -1,63 +1,65 @@
|
|||||||
|
"""
|
||||||
|
Directory setup and software file copying module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
def get_non_c_drive() -> str:
|
from module.config import InstallerConfig, detect_target_drive
|
||||||
|
|
||||||
|
logger = logging.getLogger("acc_installer")
|
||||||
|
|
||||||
|
|
||||||
|
def setup_directories_and_copy(config: Optional[InstallerConfig] = None) -> Path:
|
||||||
"""
|
"""
|
||||||
Find the first available non-C drive on the target system.
|
Create directories on the target drive and copy software packages from source.
|
||||||
"""
|
"""
|
||||||
possible_drives = [f"{letter}:\\" for letter in "DEFGH"]
|
if config is None:
|
||||||
for drive in possible_drives:
|
config = InstallerConfig()
|
||||||
if os.path.exists(drive):
|
|
||||||
return drive
|
|
||||||
return r"C:\\"
|
|
||||||
|
|
||||||
def setup_directories_and_copy(source_dir: str = r"C:\SoftwareNP",
|
print(f"[+] Selected target drive: {config.target_drive}")
|
||||||
target_drive: str = None) -> str:
|
print(f"[+] Target base directory: {config.base_dir}")
|
||||||
"""
|
|
||||||
Create directories on a non-C drive and copy files from source to NovinSoft.
|
|
||||||
"""
|
|
||||||
if not target_drive:
|
|
||||||
base_drive = get_non_c_drive()
|
|
||||||
else:
|
|
||||||
base_drive = target_drive
|
|
||||||
|
|
||||||
# Base target directory set to NovinPardazOne
|
# Ensure source directory exists
|
||||||
base_target_dir = os.path.join(base_drive, "NovinPardazOne")
|
source_dir = Path(config.source_dir)
|
||||||
acc_dir = os.path.join(base_target_dir, "NovinAcc")
|
if not source_dir.exists():
|
||||||
soft_dir = os.path.join(base_target_dir, "NovinSoft")
|
raise FileNotFoundError(f"Source directory does not exist: {source_dir}")
|
||||||
|
|
||||||
print(f"[+] Selected target drive: {base_drive}")
|
# Create destination directories
|
||||||
print(f"[+] Creating directory structure at {base_target_dir} ...")
|
config.ensure_directories()
|
||||||
|
print(f" [+] Target directories ready at {config.soft_dir}")
|
||||||
|
|
||||||
# Create NovinAcc and NovinSoft directories
|
print(f"[+] Copying files from {source_dir} to {config.soft_dir} ...")
|
||||||
os.makedirs(acc_dir, exist_ok=True)
|
|
||||||
os.makedirs(soft_dir, exist_ok=True)
|
if config.dry_run:
|
||||||
print(" [+] NovinAcc and NovinSoft directories created successfully.")
|
items = list(source_dir.iterdir())
|
||||||
|
print(f" [DRY-RUN] Would copy {len(items)} items from {source_dir} to {config.soft_dir}")
|
||||||
|
return config.soft_dir
|
||||||
|
|
||||||
|
copied_count = 0
|
||||||
|
for item in source_dir.iterdir():
|
||||||
|
dest = config.soft_dir / item.name
|
||||||
|
if item.is_dir():
|
||||||
|
shutil.copytree(item, dest, dirs_exist_ok=True)
|
||||||
|
else:
|
||||||
|
shutil.copy2(item, dest)
|
||||||
|
copied_count += 1
|
||||||
|
|
||||||
|
print(f" [+] {copied_count} file(s)/folder(s) copied successfully to {config.soft_dir}.")
|
||||||
|
return config.soft_dir
|
||||||
|
|
||||||
# Copy contents of source directory to NovinSoft
|
|
||||||
if os.path.exists(source_dir):
|
|
||||||
print(f"[+] Copying files from {source_dir} to {soft_dir} ...")
|
|
||||||
|
|
||||||
for item in os.listdir(source_dir):
|
|
||||||
s = os.path.join(source_dir, item)
|
|
||||||
d = os.path.join(soft_dir, item)
|
|
||||||
|
|
||||||
if os.path.isdir(s):
|
|
||||||
shutil.copytree(s, d, dirs_exist_ok=True)
|
|
||||||
else:
|
|
||||||
shutil.copy2(s, d)
|
|
||||||
|
|
||||||
print(f" [+] All files copied successfully to {soft_dir}.")
|
|
||||||
return soft_dir
|
|
||||||
else:
|
|
||||||
raise FileNotFoundError(f"[-] Source directory not found at {source_dir}!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.system("chcp 65001 > nul")
|
import sys
|
||||||
print("[+] Testing directory creation and software copy module ...")
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||||
|
print("[*] Running directory setup and copier module standalone ...")
|
||||||
try:
|
try:
|
||||||
target_path = setup_directories_and_copy(target_drive=r"D:\\")
|
cfg = InstallerConfig(target_drive=detect_target_drive())
|
||||||
print(f"\n[+] Operation completed successfully. Software path: {target_path}")
|
setup_directories_and_copy(cfg)
|
||||||
|
print("[+] Directory copier module completed successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n[-] Error: {e}")
|
print(f"[-] Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
@@ -1,50 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Archive extractor module for NovinAcc accounting software.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
def extract_acc_archive(base_drive: str = r"D:\\") -> str:
|
from module.config import InstallerConfig, detect_target_drive
|
||||||
# Base directory set to NovinPardazOne
|
|
||||||
base_dir = os.path.join(base_drive, "NovinPardazOne")
|
|
||||||
soft_dir = os.path.join(base_dir, "NovinSoft")
|
|
||||||
acc_dir = os.path.join(base_dir, "NovinAcc")
|
|
||||||
|
|
||||||
target_zip_name = "NovinAcc.zip"
|
logger = logging.getLogger("acc_installer")
|
||||||
source_zip_path = os.path.join(soft_dir, target_zip_name)
|
|
||||||
target_zip_path = os.path.join(acc_dir, target_zip_name)
|
|
||||||
|
|
||||||
# 1. Check if NovinSoft directory exists
|
|
||||||
if not os.path.exists(soft_dir):
|
|
||||||
raise FileNotFoundError(f"[-] Directory not found: {soft_dir}")
|
|
||||||
|
|
||||||
# 2. Check if zip file exists
|
def extract_acc_archive(config: Optional[InstallerConfig] = None,
|
||||||
if not os.path.exists(source_zip_path):
|
archive_name: str = "NovinAcc.zip") -> Path:
|
||||||
raise FileNotFoundError(f"[-] File {target_zip_name} was not found in {soft_dir}!")
|
"""
|
||||||
|
Locates NovinAcc archive in NovinSoft, moves it to NovinAcc, and extracts its contents.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = InstallerConfig()
|
||||||
|
|
||||||
# 3. Create NovinAcc directory if it doesn't exist
|
source_zip = config.soft_dir / archive_name
|
||||||
os.makedirs(acc_dir, exist_ok=True)
|
target_zip = config.acc_dir / archive_name
|
||||||
|
|
||||||
# 4. Move zip file to NovinAcc
|
config.ensure_directories()
|
||||||
print(f"[+] Moving {target_zip_name} to NovinAcc directory...")
|
|
||||||
shutil.move(source_zip_path, target_zip_path)
|
|
||||||
print(" [+] Zip file moved successfully.")
|
|
||||||
|
|
||||||
# 5. Extract archive
|
if config.dry_run:
|
||||||
print(f"[+] Extracting archive to {acc_dir}...")
|
print(f" [DRY-RUN] Would locate {archive_name} in {config.soft_dir}, move to {config.acc_dir} and extract.")
|
||||||
with zipfile.ZipFile(target_zip_path, 'r') as zip_ref:
|
return config.acc_dir
|
||||||
zip_ref.extractall(acc_dir)
|
|
||||||
print(" [+] All files extracted successfully.")
|
|
||||||
|
|
||||||
# 6. Remove temporary zip file
|
# Check if archive exists in soft_dir or already in acc_dir
|
||||||
if os.path.exists(target_zip_path):
|
archive_to_extract = None
|
||||||
os.remove(target_zip_path)
|
if source_zip.exists():
|
||||||
print(" [+] Temporary zip file deleted.")
|
archive_to_extract = source_zip
|
||||||
|
elif target_zip.exists():
|
||||||
|
archive_to_extract = target_zip
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"Archive '{archive_name}' was not found in either {config.soft_dir} or {config.acc_dir}!"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Move archive to target NovinAcc directory if not already there
|
||||||
|
if archive_to_extract != target_zip:
|
||||||
|
print(f"[+] Moving {archive_name} to NovinAcc directory...")
|
||||||
|
shutil.move(str(source_zip), str(target_zip))
|
||||||
|
print(" [+] Zip archive moved successfully.")
|
||||||
|
|
||||||
|
# Extract archive
|
||||||
|
print(f"[+] Extracting {archive_name} into {config.acc_dir} ...")
|
||||||
|
with zipfile.ZipFile(target_zip, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(config.acc_dir)
|
||||||
|
print(" [+] All archive files extracted successfully.")
|
||||||
|
|
||||||
|
# Remove temporary zip file
|
||||||
|
if target_zip.exists():
|
||||||
|
target_zip.unlink()
|
||||||
|
print(" [+] Temporary zip archive cleaned up.")
|
||||||
|
|
||||||
|
return config.acc_dir
|
||||||
|
|
||||||
return acc_dir
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.system("chcp 65001 > nul")
|
import sys
|
||||||
print("[+] Running move and extract module ...")
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||||
|
print("[*] Running NovinAcc archive extractor standalone ...")
|
||||||
try:
|
try:
|
||||||
extract_acc_archive(base_drive=r"D:\\")
|
cfg = InstallerConfig(target_drive=detect_target_drive())
|
||||||
|
extract_acc_archive(cfg)
|
||||||
|
print("[+] NovinAcc extraction completed successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[-] Error: {e}")
|
print(f"[-] Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
@@ -1,71 +1,107 @@
|
|||||||
|
"""
|
||||||
|
Extractor and automated installer runner for NovinDesk.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import zipfile
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
import zipfile
|
||||||
|
|
||||||
def install_novindesk(base_drive: str = r"D:\\") -> str:
|
from module.config import InstallerConfig, detect_target_drive
|
||||||
|
|
||||||
|
logger = logging.getLogger("acc_installer")
|
||||||
|
|
||||||
|
|
||||||
|
def automate_installer_dialog() -> bool:
|
||||||
"""
|
"""
|
||||||
Extract and automatically install NovinDesk by navigating the installer UI.
|
Automate UI dialog clicks for silent installation using pyautogui.
|
||||||
"""
|
"""
|
||||||
base_dir = os.path.join(base_drive, "NovinPardazOne")
|
|
||||||
soft_dir = os.path.join(base_dir, "NovinSoft")
|
|
||||||
|
|
||||||
zip_filename = "NovinDesk.zip"
|
|
||||||
source_zip_path = os.path.join(soft_dir, zip_filename)
|
|
||||||
extract_target_dir = os.path.join(soft_dir, "NovinDesk")
|
|
||||||
|
|
||||||
if not os.path.exists(source_zip_path):
|
|
||||||
raise FileNotFoundError(f"[-] File {zip_filename} was not found in {soft_dir}!")
|
|
||||||
|
|
||||||
os.makedirs(extract_target_dir, exist_ok=True)
|
|
||||||
|
|
||||||
print(f"[+] Extracting {zip_filename} ...")
|
|
||||||
with zipfile.ZipFile(source_zip_path, 'r') as zip_ref:
|
|
||||||
zip_ref.extractall(extract_target_dir)
|
|
||||||
print(" [+] Extraction completed successfully.")
|
|
||||||
|
|
||||||
exe_file = None
|
|
||||||
for root, dirs, files in os.walk(extract_target_dir):
|
|
||||||
for file in files:
|
|
||||||
if file.lower().endswith(".exe"):
|
|
||||||
exe_file = os.path.join(root, file)
|
|
||||||
break
|
|
||||||
|
|
||||||
if not exe_file:
|
|
||||||
raise FileNotFoundError("[-] No executable file (.exe) found!")
|
|
||||||
|
|
||||||
print(f"[+] Launching installer {os.path.basename(exe_file)} ...")
|
|
||||||
|
|
||||||
# Launch application for installation
|
|
||||||
subprocess.Popen([exe_file, "--install"])
|
|
||||||
|
|
||||||
# Wait 5 seconds for the window to fully load
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pyautogui
|
import pyautogui # type: ignore
|
||||||
|
|
||||||
# 1. Close any secondary/side dialogs
|
# 1. Close any secondary/side dialogs
|
||||||
pyautogui.press('escape')
|
pyautogui.press('escape')
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
# 2. Press Tab 4 times to navigate directly to the 'Accept & Install' button
|
# 2. Press Tab 4 times to navigate directly to 'Accept & Install'
|
||||||
for _ in range(4):
|
for _ in range(4):
|
||||||
pyautogui.press('tab')
|
pyautogui.press('tab')
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
# 3. Press Enter on the install button
|
# 3. Press Enter on the install button
|
||||||
pyautogui.press('enter')
|
pyautogui.press('enter')
|
||||||
print(" [+] Confirmation sent and automated installation started.")
|
print(" [+] Sent automated UI confirmation successfully.")
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
print(" [!] Warning: 'pyautogui' is not installed. Skipping UI automation.")
|
||||||
|
return False
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f" [-] Automation error: {err}")
|
print(f" [-] UI Automation error: {err}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def install_novindesk(config: Optional[InstallerConfig] = None,
|
||||||
|
archive_name: str = "NovinDesk.zip",
|
||||||
|
auto_ui: bool = True) -> Path:
|
||||||
|
"""
|
||||||
|
Extracts NovinDesk archive, finds the installer executable, and runs installation.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = InstallerConfig()
|
||||||
|
|
||||||
|
source_zip = config.soft_dir / archive_name
|
||||||
|
extract_target_dir = config.soft_dir / "NovinDesk"
|
||||||
|
|
||||||
|
if config.dry_run:
|
||||||
|
print(f" [DRY-RUN] Would locate {archive_name}, extract into {extract_target_dir}, and launch installer.")
|
||||||
|
return extract_target_dir
|
||||||
|
|
||||||
|
if not source_zip.exists() and not extract_target_dir.exists():
|
||||||
|
raise FileNotFoundError(f"Neither '{source_zip}' nor '{extract_target_dir}' exists!")
|
||||||
|
|
||||||
|
# Extract archive if needed
|
||||||
|
if source_zip.exists():
|
||||||
|
extract_target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
print(f"[+] Extracting {archive_name} into {extract_target_dir} ...")
|
||||||
|
with zipfile.ZipFile(source_zip, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(extract_target_dir)
|
||||||
|
print(" [+] Extraction completed successfully.")
|
||||||
|
|
||||||
|
# Locate executable
|
||||||
|
exe_file = None
|
||||||
|
for root, _, files in os.walk(extract_target_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.lower().endswith(".exe"):
|
||||||
|
exe_file = Path(root) / file
|
||||||
|
break
|
||||||
|
if exe_file:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not exe_file:
|
||||||
|
raise FileNotFoundError(f"No executable (.exe) found in {extract_target_dir}!")
|
||||||
|
|
||||||
|
print(f"[+] Launching installer {exe_file.name} ...")
|
||||||
|
subprocess.Popen([str(exe_file), "--install"])
|
||||||
|
|
||||||
|
if auto_ui:
|
||||||
|
print(" [*] Waiting 5 seconds for installer window to initialize...")
|
||||||
|
time.sleep(5)
|
||||||
|
automate_installer_dialog()
|
||||||
|
|
||||||
return exe_file
|
return exe_file
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.system("chcp 65001 > nul")
|
import sys
|
||||||
print("[+] Running NovinDesk installation module ...")
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||||
|
print("[*] Running NovinDesk installation module standalone ...")
|
||||||
try:
|
try:
|
||||||
install_novindesk(base_drive=r"D:\\")
|
cfg = InstallerConfig(target_drive=detect_target_drive())
|
||||||
|
install_novindesk(cfg)
|
||||||
|
print("[+] NovinDesk installation module completed successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[-] Error: {e}")
|
print(f"[-] Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user