:: Wizard Kit: Wrapper for launching programs and scripts.
::
:: Some features:
::   * If the OS is 64-bit then the WorkingDir is scanned for a 64-bit version of the programs
::   * Allows for centralized terminal emulation settings management
::   * Allows for smaller "launcher" scripts to be used as they will rely on this script.

@echo off
if defined DEBUG (@echo on)

:Init
setlocal EnableDelayedExpansion
title Wizard Kit: Launcher
pushd "%~dp0"
call :FindBin
call :DeQuote L_ITEM
call :DeQuote L_PATH
call :DeQuote L_TYPE

:SetVariables
rem Set variables using settings\main.py file
set "SETTINGS=%bin%\Scripts\settings\main.py"
for %%v in (ARCHIVE_PASSWORD KIT_NAME_FULL OFFICE_SERVER_IP QUICKBOOKS_SERVER_IP) do (
    set "var=%%v"
    for /f "tokens=* usebackq" %%f in (`findstr "!var!=" "%SETTINGS%"`) do (
        set "_v=%%f"
        set "_v=!_v:*'=!"
        set "%%v=!_v:~0,-1!"
    )
)
rem Set ARCH to 32 as a gross assumption and check for x86_64 status
set ARCH=32
if /i "%PROCESSOR_ARCHITECTURE%" == "AMD64" set "ARCH=64"
set "SEVEN_ZIP=%bin%\7-Zip\7za.exe"
set "CON=%bin%\ConEmu\ConEmu.exe"
set "FASTCOPY=%bin%\FastCopy\FastCopy.exe"
set "POWERSHELL=%systemroot%\system32\WindowsPowerShell\v1.0\powershell.exe"
set "PYTHON=%bin%\Python\x32\python.exe"
if %ARCH% equ 64 (
    set "SEVEN_ZIP=%bin%\7-Zip\7za64.exe"
    set "CON=%bin%\ConEmu\ConEmu64.exe"
    set "FASTCOPY=%bin%\FastCopy\FastCopy64.exe"
    set "PYTHON=%bin%\Python\x64\python.exe"
)

:UpdateTitle
rem Sets title using KIT_NAME_FULL from settings\main.py (unless %window_title% already set)
if defined window_title (
    title %window_title%
) else (
    set "window_title=%*"
    if not defined window_title set "window_title=Launcher"
    set "window_title=%KIT_NAME_FULL%: %window_title%"
    title %window_title%
)

:CheckUsage
rem Check for empty passed variables
if not defined L_TYPE (goto Usage)
if not defined L_PATH (goto Usage)
if not defined L_ITEM (goto Usage)
rem Assume if not "True" then False (i.e. undefine variable)
if /i not "%L_ELEV%" == "True" (set "L_ELEV=")
if /i not "%L_NCMD%" == "True" (set "L_NCMD=")
if /i not "%L__CLI%" == "True" (set "L__CLI=")

:RelaunchInConEmu
set RELOAD_IN_CONEMU=True
if defined ConEmuBuild          set "RELOAD_IN_CONEMU="
if defined L_NCMD               set "RELOAD_IN_CONEMU="
if "%L_TYPE%" == "Executable"   set "RELOAD_IN_CONEMU="
if "%L_TYPE%" == "PSScript"     set "RELOAD_IN_CONEMU="
if "%L_TYPE%" == "PyScript"     set "RELOAD_IN_CONEMU="

if defined RELOAD_IN_CONEMU (
    set "con_args=-new_console:n"
    rem If in DEBUG state then force ConEmu to stay open
    if defined DEBUG (set "con_args=!con_args! -new_console:c")
    start "" "%CON%" -run ""%~0" %*" !con_args! || goto ErrorUnknown
    exit /b 0
)

:CheckLaunchType
rem Jump to the selected launch type or show usage
if /i "%L_TYPE%" == "Executable"    (goto LaunchExecutable)
if /i "%L_TYPE%" == "Folder"        (goto LaunchFolder)
if /i "%L_TYPE%" == "Office"        (goto LaunchOffice)
if /i "%L_TYPE%" == "PSScript"      (goto LaunchPSScript)
if /i "%L_TYPE%" == "PyScript"      (goto LaunchPyScript)
if /i "%L_TYPE%" == "QuickBooks"    (goto LaunchQuickBooksSetup)
goto Usage

:LaunchExecutable
rem Prep
call :ExtractOrFindPath || goto ErrorProgramNotFound

rem Check for 64-bit prog (if running on 64-bit system)
set "prog=%_path%\%L_ITEM%"
if %ARCH% equ 64 (
    if exist "%_path%\%L_ITEM:.=64.%" set "prog=%_path%\%L_ITEM:.=64.%"
)
if not exist "%prog%" goto ErrorProgramNotFound

rem Run
popd && pushd "%_path%"
if defined L__CLI goto LaunchExecutableCLI
if defined L_ELEV (
    goto LaunchExecutableElev
) else (
    goto LaunchExecutableUser
)

:LaunchExecutableCLI
rem Prep
set "con_args=-new_console:n"
if defined DEBUG (set "con_args=%con_args% -new_console:c")
if defined L_ELEV (set "con_args=%con_args% -new_console:a")

rem Run
start "" "%CON%" -run "%prog%" %L_ARGS% %con_args% || goto ErrorUnknown
goto Exit

:LaunchExecutableElev
rem Prep
call :DeQuote prog
call :DeQuote L_ARGS

rem Create VB script
mkdir "%bin%\tmp" 2>nul
echo Set UAC = CreateObject^("Shell.Application"^) > "%bin%\tmp\Elevate.vbs"
echo UAC.ShellExecute "%prog%", "%L_ARGS%", "", "runas", 1 >> "%bin%\tmp\Elevate.vbs"

rem Run
"%systemroot%\System32\cscript.exe" //nologo "%bin%\tmp\Elevate.vbs" || goto ErrorUnknown
goto Exit

:LaunchExecutableUser
rem Run
start "" "%prog%" %L_ARGS% || goto ErrorUnknown
goto Exit

:LaunchFolder
rem Prep
call :ExtractOrFindPath || goto ErrorProgramNotFound

rem Run
start "" "explorer.exe" "%_path%" || goto ErrorUnknown
goto Exit

:LaunchOffice
call "%bin%\Scripts\init_client_dir.cmd" /Office
set "_odt=False"
if %L_PATH% equ 2016 (set "_odt=True")
if %L_PATH% equ 2019 (set "_odt=True")
if "%_odt%" == "True" (
    goto LaunchOfficeODT
) else (
    goto LaunchOfficeSetup
)

:LaunchOfficeODT
rem Prep
set "args=-aoa -bso0 -bse0 -bsp0 -p%ARCHIVE_PASSWORD%"
set "config=%L_ITEM%"
set "dest=%client_dir%\Office\ODT"
set "odt_exe=setup.exe"
set "source=%cbin%\_Office.7z"

rem Extract
if not exist "%source%" (goto ErrorODTSourceNotFound)
"%SEVEN_ZIP%" e "%source%" %args% -o"%dest%" %odt_exe% %config% || exit /b 1
"%systemroot%\System32\ping.exe" -n 2 127.0.0.1>nul

rem Verify
if not exist "%dest%\setup.exe" (goto ErrorODTSourceNotFound)
if not exist "%dest%\%config%" (goto ErrorODTSourceNotFound)
pushd "%dest%"

rem Run
rem # The line below jumps to ErrorUnknown even when it runs correctly??
rem start "" "setup.exe" /configure %L_ITEM% || popd & goto ErrorUnknown
rem # Going to assume it extracted correctly and blindly start setup.exe
start "" "setup.exe" /configure %config%
popd
goto Exit

:LaunchOfficeSetup
rem Prep
set "fastcopy_args=/cmd=diff /no_ui /auto_close"
set "product=%L_PATH%\%L_ITEM%"
set "product_name=%L_ITEM%"
call :GetBasename product_name || goto ErrorBasename
set "source=\\%OFFICE_SERVER_IP%\Office\%product%"
set "dest=%client_dir%\Office"

rem Verify
if not exist "%source%" (goto ErrorOfficeSourceNotFound)

rem Copy
echo Copying setup file(s) for %product_name%...
start "" /wait "%FASTCOPY%" %fastcopy_args% "%source%" /to="%dest%\"

rem Run
if exist "%dest%\%product_name%\setup.exe" (
    start "" "%dest%\%product_name%\setup.exe" || goto ErrorUnknown
) else if "%product_name:~-3,3%" == "exe" (
    start "" "%dest%\%product_name%" || goto ErrorUnknown
) else if "%product_name:~-3,3%" == "msi" (
    start "" "%dest%\%product_name%" || goto ErrorUnknown
) else (
    rem Office source not supported by this script
    goto ErrorOfficeUnsupported
)
goto Exit

:LaunchPSScript
rem Prep
call :ExtractOrFindPath || goto ErrorProgramNotFound
set "script=%_path%\%L_ITEM%"
set "ps_args=-ExecutionPolicy Bypass -NoProfile"

rem Verify
if not exist "%script%" goto ErrorScriptNotFound

rem Run
popd && pushd "%_path%"
if defined L_ELEV (
    goto LaunchPSScriptElev
) else (
    goto LaunchPSScriptUser
)

:LaunchPSScriptElev
rem Prep
call :DeQuote script

rem Create VB script
mkdir "%bin%\tmp" 2>nul
echo Set UAC = CreateObject^("Shell.Application"^) > "%bin%\tmp\Elevate.vbs"
if defined L_NCMD (
    rem use Powershell's window instead of %CON%
    echo UAC.ShellExecute "%POWERSHELL%", "%ps_args% -File "%script%"", "", "runas", 3 >> "%bin%\tmp\Elevate.vbs"
) else (
    echo UAC.ShellExecute "%CON%", "-run %POWERSHELL% %ps_args% -File "%script%" -new_console:n", "", "runas", 1 >> "%bin%\tmp\Elevate.vbs"
)

rem Run
"%systemroot%\System32\cscript.exe" //nologo "%bin%\tmp\Elevate.vbs" || goto ErrorUnknown
goto Exit

:LaunchPSScriptUser
rem Run
if defined L_NCMD (
    start "" "%POWERSHELL%" %ps_args% -File "%script%" || goto ErrorUnknown
) else (
    start "" "%CON%" -run "%POWERSHELL%" %ps_args% -File "%script%" -new_console:n || goto ErrorUnknown
)
goto Exit

:LaunchPyScript
rem Prep
call :ExtractOrFindPath || goto ErrorProgramNotFound
set "script=%_path%\%L_ITEM%"

rem Verify
"%PYTHON%" --version >nul || goto ErrorPythonUnsupported
if not exist "%script%" goto ErrorScriptNotFound

rem Run
if defined L_ELEV (
    goto LaunchPyScriptElev
) else (
    goto LaunchPyScriptUser
)

:LaunchPyScriptElev
rem Prep
call :DeQuote script

rem Create VB script
mkdir "%bin%\tmp" 2>nul
echo Set UAC = CreateObject^("Shell.Application"^) > "%bin%\tmp\Elevate.vbs"
if defined L_NCMD (
    echo UAC.ShellExecute "%PYTHON%", """%script%"" %L_ARGS%", "", "runas", 3 >> "%bin%\tmp\Elevate.vbs"
) else (
    echo UAC.ShellExecute "%CON%", "-run ""%PYTHON%"" ""%script%"" %L_ARGS% -new_console:n", "", "runas", 1 >> "%bin%\tmp\Elevate.vbs"
)

rem Run
"%systemroot%\System32\cscript.exe" //nologo "%bin%\tmp\Elevate.vbs" || goto ErrorUnknown
goto Exit

:LaunchPyScriptUser
if defined L_NCMD (
    start "" "%PYTHON%" "%script%" %L_ARGS% || goto ErrorUnknown
) else (
    start "" "%CON%" -run "%PYTHON%" "%script%" %L_ARGS% -new_console:n || goto ErrorUnknown
)
goto Exit

:LaunchQuickBooksSetup
rem Prep
call "%bin%\Scripts\init_client_dir.cmd" /QuickBooks
set "fastcopy_args=/cmd=diff /no_ui /auto_close"
set "product=%L_PATH%\%L_ITEM%"
set "product_name=%L_ITEM%"
call :GetBasename product_name || goto ErrorBasename
set "source=\\%QUICKBOOKS_SERVER_IP%\QuickBooks\%product%"
set "dest=%client_dir%\QuickBooks"

rem Verify
if not exist "%source%" (goto ErrorQuickBooksSourceNotFound)

rem Copy
echo Copying setup file(s) for %L_ITEM%...
start "" /wait "%FASTCOPY%" %fastcopy_args% "%source%" /to="%dest%\"

rem Run
if exist "%dest%\%product_name%\Setup.exe" (
    pushd "%dest%\%product_name%"
    start "" "%dest%\%product_name%\Setup.exe" || goto ErrorUnknown
    popd
) else (
    rem QuickBooks source not supported by this script
    goto ErrorQuickBooksUnsupported
)
goto Exit

:Usage
echo.
echo.Usage (via defined variables):
echo.   L_TYPE      L_PATH       L_ITEM   L_ARGS
echo.   Executable  Working Dir  Program  Args   [L_7ZIP] [L_ELEV] [L__CLI]
echo.   Folder      Folder       '.'             [L_7ZIP]
echo.   Office      Year         Product         [L_7ZIP]
echo.   PSScript    Scripts      Script          [L_7ZIP] [L_ELEV] [L_NCMD]
echo.   PyScript    Scripts      Script   Args   [L_7ZIP] [L_ELEV] [L_NCMD]
echo.   QuickBooks  Year         Product         [L_7ZIP]
echo.
echo.L_7ZIP:    Extra arguments for 7-Zip (in the :ExtractCBin label)
echo.L_ELEV:    Elevate to run as Admin
echo.L_NCMD:    Do not run script inside ConEmu (i.e. use the native window)
echo.L__CLI:    Run executable in ConEmu
echo.
goto Abort

:: Functions ::
:DeQuote
rem Code taken from http://ss64.com/nt/syntax-dequote.html
if not defined %1 (@exit /b 1)
for /f "delims=" %%a in ('echo %%%1%%') do set %1=%%~a
@exit /b 0

:ExtractCBin
rem Extract %cbin% archive into %bin%
echo Extracting "%L_PATH%"...
set "source=%cbin%\%L_PATH%.7z"
set "dest=%bin%\%L_PATH%"
set "args=-aos -bso0 -bse0 -bsp0 -p%ARCHIVE_PASSWORD%"
if defined DEBUG (set "args=-aos -p%ARCHIVE_PASSWORD%")
"%SEVEN_ZIP%" x "%source%" %args% -o"%dest%" %L_7ZIP% || exit /b 1
ping.exe -n 2 127.0.0.1>nul
exit /b 0

:FindBin
rem Checks the current directory and all parents for the ".bin" folder
rem NOTE: Has not been tested for UNC paths
set bin=
pushd "%~dp0"
:FindBinInner
if exist ".bin" (goto FindBinDone)
if "%~d0\" == "%cd%" (popd & @exit /b 1)
cd ..
goto FindBinInner
:FindBinDone
set "bin=%cd%\.bin"
set "cbin=%cd%\.cbin"
popd
@exit /b 0

:GetBasename
rem Loop over passed variable to remove all text left of the last '\' character
rem NOTE: This function should be called as 'call :GetBasename VarName || goto ErrorBasename' to catch variables that become empty.
for /f "delims=" %%a in ('echo %%%1%%') do (set "_tmp=%%~a")
:GetBasenameInner
set "_tmp=%_tmp:*\=%"
if not defined _tmp (@exit /b 1)
if not "%_tmp%" == "%_tmp:*\=%" (goto GetBasenameInner)
:GetBasenameDone
set "%1=%_tmp%"
@exit /b 0

:ExtractOrFindPath
rem Test L_PATH in the following order:
rem      1: %cbin%\L_PATH.7z (which will be extracted to %bin%\L_PATH)
rem      2: %bin%\L_PATH
rem      3. %L_PATH%         (i.e. treat L_PATH as an absolute path)
rem NOTE: This function should be called as 'call :ExtractOrFindPath || goto ErrorProgramNotFound' to catch invalid paths.
set _path=
if exist "%cbin%\%L_PATH%.7z" (
    call :ExtractCBin
) else if exist "%cbin%\%L_PATH%\%L_ITEM:~0,-4%.7z" (
    call :ExtractCBin
)
if exist "%bin%\%L_PATH%" (set "_path=%bin%\%L_PATH%")
if not defined _path (set "_path=%L_PATH%")
rem Raise error if path is still not available
if not exist "%_path%" (exit /b 1)
exit /b 0

:: Errors ::
:ErrorBasename
echo.
echo ERROR: GetBasename resulted in an empty variable.
goto Abort

:ErrorNoBin
echo.
echo ERROR: ".bin" folder not found.
goto Abort

:ErrorODTSourceNotFound
echo.
echo ERROR: Office Deployment Tool source not found.
goto Abort

:ErrorOfficeSourceNotFound
echo.
echo ERROR: Office source "%L_ITEM%" not found.
goto Abort

:ErrorOfficeUnsupported
rem Source is not an executable nor is a folder with a setup.exe file inside. Open explorer to local setup file(s) instead.
echo.
echo ERROR: Office version not supported by this script.
start "" "explorer.exe" "%client_dir%\Office"
goto Abort

:ErrorPythonUnsupported
rem The Windows installation lacks Windows update KB2999226 needed to run Python
echo.
echo ERROR: Failed to run Python, try installing Windows update KB2999226.
echo NOTE: That update is from October 2015 so this system is SEVERELY outdated
if exist "%bin%\..\Installers\Extras\Windows Updates" (
  start "" "explorer.exe" "%bin%\..\Installers\Extras\Windows Updates"
)
goto Abort

:ErrorQuickBooksSourceNotFound
echo.
echo ERROR: QuickBooks source "%L_ITEM%" not found.
goto Abort

:ErrorQuickBooksUnsupported
rem Source is not an executable nor is a folder with a setup.exe file inside. Open explorer to local setup file(s) instead.
echo.
echo ERROR: QuickBooks version not supported by this script.
start "" "explorer.exe" "%client_dir%\QuickBooks"
goto Abort

:ErrorProgramNotFound
echo.
echo ERROR: Program "%prog%" not found.
goto Abort

:ErrorScriptNotFound
echo.
echo ERROR: Script "%script%" not found.
goto Abort

:ErrorUnknown
echo.
echo ERROR: Unknown error encountered.
goto Abort

:Abort
rem Handle color theme for both the native console and ConEmu
if defined ConEmuBuild (
    color c4
) else (
    color 4e
)
echo Aborted.
echo.
echo DETAILS: L_TYPE: %L_TYPE%
echo.         L_PATH: %L_PATH%
echo.         L_ITEM: %L_ITEM%
echo.         L_ARGS: %L_ARGS%
echo.         L_7ZIP: %L_7ZIP%
echo.         L_ELEV: %L_ELEV%
echo.         L_NCMD: %L_NCMD%
echo.         L__CLI: %L__CLI%
echo.         CON:    %CON%
echo.         DEBUG:  %DEBUG%
echo.         PYTHON: %PYTHON%
echo Press any key to exit...
pause>nul
rem reset color and reset errorlevel to 0
rem NOTE: This is done to avoid causing a ErrorLaunchCMD in the launcher.cmd
color 07
goto Exit

:: Cleanup and exit ::
:Exit
popd
endlocal
exit /b %errorlevel%
