@echo off
REM ===============================================================================
REM
REM  Script arguments
REM  /currentos:<driveletter>
REM      Specify the current drive letter (under WinPE) of the OS partition REQUIRED
REM
REM  NOTE: The log is printed to console by default.  To write log to file, please
REM  redirect the output to a log file.  For example:
REM     osletter7.bat /targetletter:s /currentos:t > t:\osletter7.log
REM
REM -------------------------------------------------------------------------------

setlocal
set BadUsage=
set CurrentOsDriveLetter=
set TargetSystemDriveLetter=
set NewOSDriveLetter=
set TempOutFile=%TEMP%\osletter7.bat.temp1.out
set ValidDriveLetters=C D E F G H I J K L M N O P Q R S T U V W X Y Z

if /I "%1" == "" (
    Call :PrintUsage
    exit /b 1
)

if /I "%1" == "/?" (
    Call :PrintUsage
    exit /b 1
)

REM ----- Print log file header ------
echo.
echo Script processing started for command: "%0 %*".
echo Parsing arguments...

REM ----- parse command line arguments ------
:ParseArgumentLoop
    if /I "%1" == "" (
        goto :ParseArgumentLoopEnd
    )

    if /I "%1" == "/?" (
        Call :PrintUsage
        exit /b 1
    )

    Call :ParseArgument  %1
    if "%BadUsage%" == "1" (
        goto :HandleBadUsage
    )

    shift /1
    goto :ParseArgumentLoop
:ParseArgumentLoopEnd

REM ----- argument checks ------
Call :ValidateArgument
if "%BadUsage%" == "1" (
    goto :HandleBadUsage
)

if not exist %CurrentOsDriveLetter%:\Windows\System32\Config\SYSTEM (
    echo ERROR - Drive "%CurrentOsDriveLetter%" is not a valid OS drive - OS image not found.
    goto :HandleBadUsage
)

REM Output command arguments to log file
echo -----------------------------------------------------------------------
echo ---------------Executing with the following arguments------------------
echo -----------------------------------------------------------------------
echo Current OS partition: %CurrentOsDriveLetter%
echo Target system partition letter: %TargetSystemDriveLetter%
echo -----------------------------------------------------------------------

REM ----- Main script processing ----
Call :FixRegistry
if "%BadUsage%" == "1" (
    goto :HandleBadUsage
)
if ERRORLEVEL 1 (
    goto :Failure
)


REM --------------------------------------------------------------------------------------
REM End of the main function (in case of success)
REM --------------------------------------------------------------------------------------
if exist %TempOutFile% del /q %TempOutFile%
echo Script completed successfully.
exit /b 0

REM --------------------------------------------------------------------------------------
REM Exit the script in case of failure.
REM --------------------------------------------------------------------------------------
:Failure
    if exist %TempOutFile% del /q %TempOutFile%
    echo Script failed!
    exit/b 1

REM --------------------------------------------------------------------------------------
REM Function - Parse an argument
REM    Output: set BadUsage=1 if parsing failed
REM --------------------------------------------------------------------------------------
:ParseArgument
    set ArgumentName=
    set ArgumentValue=
    for /F "delims=: tokens=1" %%i in ("%1") do set ArgumentName=%%i
    for /F "delims=: tokens=2" %%i in ("%1") do set ArgumentValue=%%i
    for /F "delims=: tokens=3" %%i in ("%1") do (
        if not "%%1" == "" (
            REM This is the error case that there are more than two tokens in the
            REM argument string (e.g. "currentsystem:s:abc")
            echo Error - Invalid syntax: %1
            set BadUsage=1
            exit /b 1
        )
    )

        if /I "%ArgumentName%" == "/Targetletter" (
            set TargetSystemDriveLetter=%ArgumentValue%
        ) else (
            if /I "%ArgumentName%" == "/CurrentOS" (
                set CurrentOsDriveLetter=%ArgumentValue%
            ) else (
	        echo Error - Invalid syntax: %1
        	set BadUsage=1
                exit /b 1
            )
        )    

    exit /b 0

REM --------------------------------------------------------------------------------------
REM Function - Check arguments for requiered options.
REM    Input:
REM    Output: set BadUsage=1 if validation failed
REM --------------------------------------------------------------------------------------
:ValidateArgument
    if "%TargetSystemDriveLetter%"  == ""  (
        echo ERROR - No /targetLetter option specified on the command line.
        set BadUsage=1
    ) else (
      Call :IsValidDriveLetter  TargetLetter  %TargetSystemDriveLetter%  TargetSystemDriveLetter
    )

    if "%CurrentOsDriveLetter%"     == ""  (
        echo ERROR - No /currentos option specified on the command line.
        set BadUsage=1
    ) else (
      Call :IsValidDriveLetter  CurrentOS  %CurrentOsDriveLetter%  CurrentOsDriveLetter
    )

    exit /b 0

REM --------------------------------------------------------------------------------------
REM Function - Display usage for help.
REM    Input:
REM    Output:
REM --------------------------------------------------------------------------------------
:PrintUsage
    echo This script can be used to change the OS drive letters when
    echo deploying a WIM image before reboot.
    echo.
    echo osletter7[.cmd] /TargetLetter:^<DriveLetter^> /CurrentOS:^<DriveLetter^>
    echo.
    echo   /TargetLetter:^<DriveLetter^>
    echo     Specify the OS drive letter under the system
    echo.
    echo   /CurrentOS:^<DriveLetter^>
    echo     Specify the current drive letter (under WinPE) of the OS partition
    echo.
    echo Examples:
    echo   osletter7 /targetletter:s /currentos:t 
    echo      Execute the script.
    echo.
    echo   osletter7 /targetletter:s /currentos:t ^> t:\dl.log
    echo      Execute the script and redirect output to a log file.
    echo.
    exit /b 0

REM --------------------------------------------------------------------------------------
REM Function - Print help message in case of bad usage
REM    Input:
REM    Output:
REM --------------------------------------------------------------------------------------
:HandleBadUsage
    echo.
    echo Type "DriveLetter /?" for help.
    exit /b 1

REM --------------------------------------------------------------------------------------
REM Function - Check whether a drive letter from a command line argument is valid or not.
REM    Input: %1 - Name of the command line argument (e.g. CurrentSystem)
REM           %2 - the drive letter (e.g. C)
REM           %3 - Name of the global variable to set (e.g. CurrentSystemDriveLetter)
REM    Output:  set BadUsage=1 and errorlevel=1 if the input drive letter is invalid
REM --------------------------------------------------------------------------------------
:IsValidDriveLetter
    for %%i in (%ValidDriveLetters%) do (
        if /I "%%i" == "%2"  (
            set %3=%%i
            exit /b 0
        )
    )

    echo ERROR - Invalid drive letter "%2" entered for the command line argument "%1"
    set BadUsage=1
    exit /b 1

REM --------------------------------------------------------------------------------------
REM Function - Check whether a drive letter is valid or not.
REM    Input: %1 - the drive letter
REM    Output:  set errorlevel=1 if the drive letter is invalid
REM --------------------------------------------------------------------------------------
:IsValidDriveLetter2
    for %%i in (%ValidDriveLetters%) do (
        if /I "%%i" == "%1"  (
            exit /b 0
        )
    )
    exit /b 1

REM --------------------------------------------------------------------------------------
REM Function - Fix drive letters in registry
REM    Input: arguments in global variables
REM    Output:
REM    Return: set errorlevel to 1 in case of failure
REM --------------------------------------------------------------------------------------
:FixRegistry
    set SystemHiveLoaded=
    set FixRegistryErrorLevel=

    echo Fixing drive letters in registry...

    Call :GetNewOsDriveLetter
    if errorlevel 1 (
        goto :FixRegistryExitWithFailure
    )

    echo Preparing to fix registry entries...

    Call :FindVolumeEntryByDriveLetter  %CurrentOsDriveLetter%      OSVolumeValue
    if errorlevel 1 (
        goto :FixRegistryExitWithFailure
    )


    REM -- At this point, OSVolumeValue is something like 000000080014E70400000000.


    reg.exe load HKU\TEMP %CurrentOsDriveLetter%:\Windows\System32\Config\SYSTEM  > %TempOutFile%
    if ERRORLEVEL 1 (
        echo Command failed: reg.exe load HKU\TEMP %CurrentOsDriveLetter%:\Windows\System32\Config\SYSTEM
        goto :FixRegistryExitWithFailure
    ) else (
        set SystemHiveLoaded=1
    )

    REM -- create the key first so that we can successfully 
    REM -- delete, in case it did not exist.
    reg add HKU\TEMP\MountedDevices /f > %TempOutFile%
    if ERRORLEVEL 1 (
        echo Command failed: reg add HKU\TEMP\MountedDevices /f
        goto :FixRegistryExitWithFailure
    )

    reg delete HKU\TEMP\MountedDevices /f > %TempOutFile%
    if ERRORLEVEL 1 (
        echo Command failed: reg delete HKU\TEMP\MountedDevices /f
        goto :FixRegistryExitWithFailure
    )

    reg add HKU\TEMP\MountedDevices /f > %TempOutFile%
    if ERRORLEVEL 1 (
        echo Command failed: reg add HKU\TEMP\MountedDevices /f
        goto :FixRegistryExitWithFailure
    )

    reg add HKU\TEMP\MountedDevices /v \DosDevices\%NewOSDriveLetter%: /t REG_BINARY /d %OSVolumeValue% /f > %TempOutFile%
    if ERRORLEVEL 1 (
        echo Command failed: reg add HKU\TEMP\MountedDevices /v \DosDevices\%NewOSDriveLetter%: /t REG_BINARY /d %OSVolumeValue% /f
        echo ERROR - Failed to write to System registry hive
        goto :FixRegistryExitWithFailure
    ) else (
        echo Successfully wrote OS volume device with the new drive letter.
    )


    reg.exe unload HKU\TEMP > %TempOutFile%
    if ERRORLEVEL 1 (
        echo ERROR - Failed to save the System registry hive: %CurrentOsDriveLetter%:\Windows\System32\Config\SYSTEM.
        goto :FixRegistryExitWithFailure
    ) else (
        echo Successfully saved registry hive.
        set SystemHiveLoaded=
    )

    goto :FixRegistryEnd

:FixRegistryExitWithFailure
    set FixRegistryErrorLevel=1
:FixRegistryEnd
    if "%SystemHiveLoaded%" == "1" (
        reg.exe unload HKU\TEMP > %TempOutFile%
        if ERRORLEVEL 1 (
            echo Failed unload the SYSTEM hive, error ignored.
        )
    )


    exit /b %FixRegistryErrorLevel%

REM --------------------------------------------------------------------------------------
REM Function - Read offline register SOFTWARE\Microsoft\Windows NT\CurrentVersion RegValue
REM            "PathName", and parse the new OS driveletter from it.
REM
REM    Input:
REM    Output: set NewOSDriveLetter to the new OS driveletter
REM    Return: set errorlevel to 1 in case of failure
REM --------------------------------------------------------------------------------------
:GetNewOsDriveLetter
    set NewOSDriveLetter=%TargetSystemDriveLetter%

    Call :IsValidDriveLetter2 %NewOSDriveLetter%
    if ERRORLEVEL 1 (
        echo Failed to parse the new OS drive letter.
        exit /b 1
    ) else (
        echo Valid OS drive letter.
    )


    exit /b 0

REM --------------------------------------------------------------------------------------
REM Function - Search HKLM\SYSTEM\MountedDevices, find the volume device name and value by driveletter.
REM
REM    Input: %1 - The drive letter
REM           %2 - Name of the variable to be set to the volume device value
REM    Output: set variable %3
REM    Return: set errorlevel to 1 in case of failure
REM --------------------------------------------------------------------------------------
:FindVolumeEntryByDriveLetter
    set %2=
    set VolumeDeviceValue=
    set DeviceDriveName=\DosDevices\%1:

    reg query HKLM\SYSTEM\MountedDevices /v %DeviceDriveName% > %TempOutFile%
    if ERRORLEVEL 1 (
        echo Command failed: reg query HKLM\SYSTEM\MountedDevices /v %DeviceDriveName%
        goto :FindVolumeEntryByDriveLetterEnd
    )

    for /F "skip=2 tokens=3" %%i in (%TempOutFile%) do (
        REM -- set VolumeDeviceValue to the REG_BINARY value (something like
        REM -- 000000080014E70400000000) of the volume device.
        set VolumeDeviceValue=%%i
        goto :FindVolumeEntryByDriveLetterEnd
    )

:FindVolumeEntryByDriveLetterEnd
    if "%VolumeDeviceValue%" == "" (
        echo Error - Failed to get registry drive mapping for drive: %1
        exit /b 1
    )

    set %2=%VolumeDeviceValue%
    exit /b 0
