/*
MOVELATR v1.02, (c)2000-2008 Jeremy Collake
All Rights Reserved.
jeremy@bitsum.com
http://www.bitsum.com

Freeware from Bitsum Technologies.
Use of this code must be accompanied with credit. Commercial use prohibited
without authorization from Jeremy Collake.

------------------------------------------------------------------------------------------
This software is provided as-is, without warranty of ANY KIND,
either expressed or implied, including but not limited to the implied
warranties of merchantability and/or fitness for a particular purpose.
The author shall NOT be held liable for ANY damage to you, your
computer, or to anyone or anything else, that may result from its use,
or misuse. Basically, you use it at YOUR OWN RISK.
-------------------------------------------------------------------------------------------

This simple console mode application will set up files or directories
to be renamed, deleted, or replaced at bootime. This is particularly
useful when replacing files that are persistently in use when the
operating system is booted. Movelatr also displays and clears
pending move operations. Full C++ source code is included.

   USAGE:

  MOVELATR
        = enumerates pending move operations.
  MOVELATR source destination
    = sets up move of source to destination at boot.
  MOVELATR source /D
    = deletes source at boot.
  MOVELATR /C
    = clear all pending move operations.

Moving of a directory to another non-existant one on the same
partition is supported (this may be considered a rename).

Moving of files from one volume to another is not supported
under NT/2k. You must first copy the file to the volume as the
destination, and then use movelatr.

Deletion of directories will not occur unless the directories
are empty.

Wildcards are not supported at this time.

Revision History:
  v1.02 Added SetAllowProtectedRenames for WFP protected files.
         + credit for this change goes to Peter Kovacs.
  v1.01        Added awareness of WFP/SFC (Win2000+).
            Cleaned up portions of code
  v1.00        Initial Release

*/

#include<stdio.h>
#include<conio.h>
#include<windows.h>
#include<winuser.h>

#define ERROR_CMDLINE                   1
#define ERROR_SOURCEFILE                2
#define ERROR_DESTFILE                  3
#define ERROR_ABORTED                   4
#define ERROR_DIRFILECONFLICT                        5
#define ERROR_DIREXISTS                              6
#define ERROR_DRIVEINCONSISTENT                                 7
#define ERROR_MOVE                      8
#define ERROR_FAILEDCLEAR               9
#define ERROR_WILDCARD                  10
#define ERROR_SFP                       11

bool IsWin9x()
{
        OSVERSIONINFO vInfo;
        vInfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
        GetVersionEx(&vInfo);
        return vInfo.dwPlatformId==VER_PLATFORM_WIN32_NT?false:true;
}

bool DoesDirectoryExist(char *pDirName) {
        unsigned int nAttributes=0;
        if((nAttributes=GetFileAttributes(pDirName))==-1)
                return false;
        if((nAttributes&FILE_ATTRIBUTE_DIRECTORY)!=0)
                return true;
        else return false;
}

bool DoesFileExist(char *pFileName)
{
        unsigned int nAttributes=0;
        if((nAttributes=GetFileAttributes(pFileName))==-1)
                return false;
        return true;
}

typedef DWORD (__stdcall *PFNSfcIsFileProtected)(HANDLE, LPCWSTR);
bool IsProtectedFile(char *pFileName)
{
        WCHAR UniFileName[MAX_PATH];
        HINSTANCE hSFC;
        PFNSfcIsFileProtected SfcIsFileProtected;
        bool bR;
        hSFC=LoadLibrary("SFC.DLL");
        if(!hSFC)
        {
                return false;
        }
        SfcIsFileProtected=(PFNSfcIsFileProtected)GetProcAddress(hSFC,"SfcIsFileProtected");
        if(!SfcIsFileProtected)
        {
                FreeLibrary(hSFC);
                return false;
        }
        MultiByteToWideChar(CP_ACP,0,pFileName,strlen(pFileName),UniFileName,MAX_PATH);
        bR=SfcIsFileProtected(NULL,UniFileName);
        FreeLibrary(hSFC);
        return bR;
}

// SetAllowedProtectedRanames
// Thanks Peter Kovacs for this change.
bool SetAllowProtectedRenames()
{
        HKEY hKey;
    DWORD val = 1;
        if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager"0, KEY_SET_VALUE, &hKey)!=ERROR_SUCCESS)
        {
         return false;
        }
    RegSetValueEx(hKey, "AllowProtectedRenames"0, REG_DWORD, (const byte*)&val, sizeof(DWORD));
    RegCloseKey(hKey);
    return true;
}

char *ExtractAsciiString(char *wc, char *pszKey)
{
        while(*wc)
        {
                *pszKey=*wc;
                pszKey++;
                wc++;
        }
        *pszKey=0;
        return wc+1;
}

// returns true if equal found, false if not
bool ExtractToEqual(char *pszLine, char *pszFile, char **pszNewLinePos)
{
        while(*pszLine && *pszLine!='=')
        {
                *pszFile=*pszLine;
                pszLine++;
                pszFile++;
        }
        if(*pszLine=='=')
        {
                *pszNewLinePos=pszLine+1;
                return true;
        }
        *pszNewLinePos=pszLine;
        return false;
}

char *pszMoveKey="SYSTEM\\CurrentControlSet\\Control\\Session Manager";
char *pszValueName="PendingFileRenameOperations";
#define MAX_VALUESIZE 8192

bool ClearPendingMoves()
{
        HKEY hKey;
        DWORD dispo;
        DWORD dwSize=MAX_VALUESIZE;
        DWORD dwType=REG_BINARY;
        char szNewData=0;
        if(!IsWin9x())
        {
                if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,pszMoveKey,0,0,0,KEY_WRITE,0,&hKey,&dispo)==ERROR_SUCCESS)
                {
                        if((dispo=RegDeleteValue(hKey,pszValueName))!=ERROR_SUCCESS)
                        {
                                RegCloseKey(hKey);
                                if(dispo==ERROR_FILE_NOT_FOUND)
                                {
                                        return true;
                                }
                                return false;
                        }
                        RegCloseKey(hKey);
                        return true;
                }
                return false;
        }
        return WritePrivateProfileSection("rename",(char *)&szNewData,"wininit.ini");

}
bool ShowPendingMoves()
{
        HKEY hKey;
        DWORD dispo;
        DWORD dwSize=MAX_VALUESIZE;
        DWORD dwType=REG_BINARY;
        char *pBuffer;
        char *pAscii;
        char *pTempBuffer;
        unsigned int nMoveNum=0;
        if(!IsWin9x())
        {
                pBuffer=new char[MAX_VALUESIZE];
                pAscii=new char[(MAX_VALUESIZE+1)/2];
                if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,pszMoveKey,0,0,0,KEY_READ,0,&hKey,&dispo)==ERROR_SUCCESS)
                {
                        if(RegQueryValueEx(hKey,pszValueName,0,&dwType,(BYTE *)pBuffer,&dwSize)!=ERROR_SUCCESS)
                        {
                                printf("\n None.");
                                RegCloseKey(hKey);
                                delete pBuffer;
                                delete pAscii;
                                return false;
                        }
                        RegCloseKey(hKey);
                        pTempBuffer=pBuffer;
                        while(1)
                        {
                                *pAscii=0;
                                pTempBuffer=ExtractAsciiString(pTempBuffer,pAscii);
                                if(!(*pAscii))
                                {
                                        delete pBuffer;
                                        delete pAscii;
                                        return true;
                                }
                                printf("\n %d.] %s => ", nMoveNum,(pAscii+4));
                                *pAscii=0;
                                pTempBuffer=ExtractAsciiString(pTempBuffer,pAscii);
                                printf("%s", *pAscii?(pAscii+4):"NULL");
                                nMoveNum++;
                        }
                }
                else
                {
                        delete pBuffer;
                        delete pAscii;
                        printf("\n Error opening HKLM\\%s", pszMoveKey);
                        return false;
                }
        }
        else
        {
                FILE *hWinInit;
                pBuffer=new char[MAX_PATH*2];
                pAscii=new char[MAX_PATH];
                GetWindowsDirectory(pBuffer,MAX_PATH);
                strcat(pBuffer,"\\wininit.ini");
                hWinInit=fopen(pBuffer,"r");
                if(hWinInit)
                {
                        // find [rename] section
                        while(!strstr(strupr(pBuffer),"[RENAME]") && fgets(pBuffer,MAX_PATH*2,hWinInit));
                        while(fgets(pBuffer,MAX_PATH*2,hWinInit))
                        {
                                pTempBuffer=pBuffer;
                                if(!ExtractToEqual(pTempBuffer,pAscii,&pTempBuffer))
                                {
                                        break;
                                }
                                if(*(pTempBuffer+strlen(pTempBuffer)-1)=='\n')
                                {
                                        *(pTempBuffer+strlen(pTempBuffer)-1)=0;
                                }
                                printf("\n %d.] %s => %s", nMoveNum, pTempBuffer, pAscii);
                                nMoveNum++;
                        }
                        fclose(hWinInit);
                }
                delete pBuffer;
                delete pAscii;
        }
        if(!nMoveNum)
        {
                printf("\n None.");
        }
        return true;
}


int main(int argc, char **argv)
{
        char szSourceFile[MAX_PATH];
        char szDestFile[MAX_PATH];
        char szTemp[MAX_PATH];
        char *szFile;
        bool bDelete=false;
        bool bNT=!IsWin9x();

        printf("\n MoveLatr v1.02, (c)2000 Jeremy Collake");
        printf("\n http://www.bitsum.com");
        printf("\n Kernel is %s\n", bNT?"WinNT":"Win9x");
        if(argc<3)
        {
                if(argc==2 && !strcmpi(argv[1],"/C"))
                {
                        int nR;
                        printf("\n Clearing pending moves .. %s\n", (nR=ClearPendingMoves())?"Success":"Failed!");
                        return nR?ERROR_SUCCESS:ERROR_FAILEDCLEAR;
                }
                printf("\n Usage: MOVELATR");
                printf("\n         = enumerates pending move operations.");
                printf("\n        MOVELATR source dest");
                printf("\n         = sets up move of source to dest at reboot.");
                printf("\n        MOVELATR source /d");
                printf("\n         = sets up delete of source at reboot.");
                printf("\n        MOVELATR /c");
                printf("\n         = clears all pending move operations.\n");
                printf("\n Pending move operations:");
                ShowPendingMoves();
                printf("\n");
                return ERROR_CMDLINE;
        }
        if(strstr(argv[1],"*") || strstr(argv[2],"*") || strstr(argv[1],"?") || strstr(argv[2],"*"))
        {
                printf("\n Wildcards not supported at this time.\n");
                return ERROR_WILDCARD;
        }
        if(strstr(strupr(argv[2]),"/D"))
        {
                bDelete=true;
                strcpy(szDestFile,"NULL");
        }
        if(!GetFullPathName(argv[1],MAX_PATH,szSourceFile,&szFile))
        {
                strcpy(szSourceFile,argv[1]);
        }
        if(!bDelete)
        {
                if(!GetFullPathName(argv[2],MAX_PATH,szDestFile,NULL))
                {
                        strcpy(szDestFile,argv[2]);
                }
        }
        if(IsWin9x())
        {
                if(GetShortPathName(szSourceFile,szTemp,MAX_PATH))
                {
                        strcpy(szSourceFile,szTemp);
                }
                if(!bDelete)
                {
                        if(GetShortPathName(szDestFile,szTemp,MAX_PATH))
                        {
                                strcpy(szDestFile,szTemp);
                        }
                }
        }
        if(!DoesFileExist(szSourceFile))
        {
                printf("\n Source file %s does not exist!\n", szSourceFile);
                return ERROR_SOURCEFILE;
        }
        if(!bDelete)
        {
                if(DoesDirectoryExist(szSourceFile))
                {
                        if(DoesDirectoryExist(szDestFile))
                        {
                                printf("\n Destination directory already exists. Cannot move directory.\n");
                                return ERROR_DIREXISTS;
                        }
                        if(DoesFileExist(szDestFile))
                        {
                                printf("\n Error, source is a directory and destination is a file.\n");
                                return ERROR_DIRFILECONFLICT;
                        }
                }
                if(szSourceFile[1]==':' && szDestFile[1]==':' && (toupper(szSourceFile[0])!=toupper(szDestFile[0])) && bNT)
                {
                        printf("\n The Source and destination must be on the same drive.\n");
                        return ERROR_DRIVEINCONSISTENT;
                }
                if(DoesDirectoryExist(szDestFile))
                {
                        if(szDestFile[strlen(szDestFile)-1]!='\\')
                        {
                                strcat((char *)&szDestFile,"\\");
                        }
                        strcat(szDestFile,szFile);
                }
                if(DoesFileExist(szDestFile))
                {
                        if(IsProtectedFile(szDestFile))
                        {
                                printf("\n Entering AllowProtectedRenames=1 to registry for WFP protected move operation.");
                                if(!SetAllowProtectedRenames())
                                {
                                        printf("\n ERROR: Setting registry key.");
                                        return ERROR_SFP;
                                }
                        }
                        char ch=0;
                        printf("\n Destination already exists, are you sure you want to overwrite it? [Y/N] ");
                        while(ch!='Y' && ch!='N')
                        {
                                ch=toupper(getch());
                        }
                        putch(ch);
                        if(toupper(ch)=='N')
                        {
                                printf("\n Operation aborted.\n");
                                return ERROR_ABORTED;
                        }
                }
        } // end if !bDelete

        if(!bNT)
        {
                if(!bDelete)
                {
                        WritePrivateProfileString("rename","NUL",szDestFile,"wininit.ini");
                        WritePrivateProfileString("rename",szDestFile,szSourceFile,"wininit.ini");
                }
                else
                {
                        WritePrivateProfileString("rename","NUL",szSourceFile,"wininit.ini");
                }
        }
        else
        {
                if(!bDelete)
                {
                        if(!MoveFileEx(szDestFile,NULL,MOVEFILE_DELAY_UNTIL_REBOOT)
                                ||
                        !MoveFileEx(szSourceFile,szDestFile,MOVEFILE_DELAY_UNTIL_REBOOT))
                        {
                                printf("\n Error setting up %s to be moved. Error code: %d\n",
                                        DoesDirectoryExist(szSourceFile)?"directory":"file",GetLastError());
                                return ERROR_MOVE;
                        }
                }
                else
                {
                        if(!MoveFileEx(szSourceFile,NULL,MOVEFILE_DELAY_UNTIL_REBOOT))
                        {
                                printf("\n Error setting up %s to be deleted. Error code: %d\n",
                                        DoesDirectoryExist(szSourceFile)?"directory":"file",GetLastError());
                                return ERROR_MOVE;
                        }
                }
        }
        printf("\n Source: %s\n Destination: %s\n %s will be completed at system boot.\n",
                szSourceFile, szDestFile,bDelete?"Deletion":"Move");
        return ERROR_SUCCESS;
}