SCRNSAVE.C

/*++ 

Copyright (c) 1997 Microsoft Corporation

Module Name:

scrnsave.c

Abstract:

This source file implements the seven required functions for a
Windows NT 5.0 migration DLL. The DLL demonstrates how the
interface works by performing the Windows 9x screen saver
upgrade.

The source here is a subset of the actual screen saver DLL
that ships with Windows NT Setup.

This sample demonstrates:

- How to detect installation of your application

- A typical implementation of QueryVersion, Initialize9x,
MigrateUser9x, MigrateSystem9x, InitializeNT,
MigrateUserNT and MigraetSystemNT

- How to provide language-dependent incompatibility
messages to the user

- How to remove Setup's incompatibility messages via
[Handled] section

- Saving settings to a temporarly file in the working
directory

- Mix use of ANSI and UNICODE

- Use of the SetupLogError API

- Deleting files

- Handling the move from system to system32

Author:

Jim Schmidt 11-Apr-1997

Revision History:


--*/

#include "pch.h"

//
// Constants
//

#define CP_USASCII 1252
#define END_OF_CODEPAGES -1

//
// Code page array
//

INT g_CodePageArray[] = {
CP_USASCII,
END_OF_CODEPAGES
};

//
// Multi-sz (i.e., double-nul terminated) list of files to find
//

CHAR g_ExeNamesBuf[] = "Blank Screen.scr\0"
"Curves and Colors.scr\0"
"Flying Through Space.scr\0"
"Scrolling Marquee.scr\0"
"Mystify Your Mind.scr\0";

//
// Copies of the working directory and source directory
//

LPSTR g_WorkingDirectory = NULL;
LPSTR g_SourceDirectories = NULL; // multi-sz
LPSTR g_SettingsFile = NULL;
LPSTR g_MigrateDotInf = NULL;

//
// Registry locations and INI file sections
//

#define REGKEY_DESKTOP "Control Panel\\Desktop"
#define FULL_REGKEY_DESKTOP "HKR\\Control Panel\\Desktop"
#define FULL_REGKEY_PWD_PROVIDER "HKLM\\System\\CurrentControlSet\\Control\\PwdProvider\\SCRSAVE"

#define REGVAL_SCREENSAVEACTIVE "ScreenSaveActive"
#define REGVAL_SCREENSAVELOWPOWERACTIVE "ScreenSaveLowPowerActive"
#define REGVAL_SCREENSAVELOWPOWERTIMEOUT "ScreenSaveLowPowerTimeout"
#define REGVAL_SCREENSAVEPOWEROFFACTIVE "ScreenSavePowerOffActive"
#define REGVAL_SCREENSAVEPOWEROFFTIMEOUT "ScreenSavePowerOffTimeout"
#define REGVAL_SCREENSAVETIMEOUT "ScreenSaveTimeOut"
#define REGVAL_SCREENSAVEUSEPASSWORD "ScreenSaveUsePassword"

#define REGVAL_LOWPOWERACTIVE "LowPowerActive"
#define REGVAL_LOWPOWERTIMEOUT "LowPowerTimeout"
#define REGVAL_POWEROFFACTIVE "PowerOffActive"
#define REGVAL_POWEROFFTIMEOUT "PowerOffTimeout"
#define REGVAL_SCREENSAVERISSECURE "ScreenSaverIsSecure"

//
// State variables
//

BOOL g_FoundPassword = FALSE;
LPCSTR g_User;
CHAR g_UserNameBuf[MAX_PATH];
HANDLE g_hHeap;



BOOL
WINAPI
DllMain (
IN HANDLE DllInstance,
IN ULONG ReasonForCall,
IN LPVOID Reserved
)
{
switch (ReasonForCall) {

case DLL_PROCESS_ATTACH:
//
// We don't need DLL_THREAD_ATTACH or DLL_THREAD_DETACH messages
//
DisableThreadLibraryCalls (DllInstance);

//
// Global init
//
g_hHeap = GetProcessHeap();

if (!MigInf_Initialize()) {
return FALSE;
}

// Open log; FALSE means do not delete existing log
SetupOpenLog (FALSE);
break;

case DLL_PROCESS_DETACH:
MigInf_CleanUp();

// Clean up strings
if (g_WorkingDirectory) {
HeapFree (g_hHeap, 0, g_WorkingDirectory);
}
if (g_SourceDirectories) {
HeapFree (g_hHeap, 0, g_SourceDirectories);
}
if (g_SettingsFile) {
HeapFree (g_hHeap, 0, g_SettingsFile);
}
if (g_MigrateDotInf) {
HeapFree (g_hHeap, 0, g_MigrateDotInf);
}

SetupCloseLog();

break;
}

return TRUE;
}


LONG
CALLBACK
QueryVersion (
OUT LPCSTR *ProductID,
OUT LPUINT DllVersion,
OUT LPINT *CodePageArray, OPTIONAL
OUT LPCSTR *ExeNamesBuf, OPTIONAL
LPVOID Reserved
)
{
//
// First, we do some preliminary investigation to see if
// our components are installed.
//

if (!GetScrnSaveExe()) {
//
// We didn't detect any components, so we return
// ERROR_NOT_INSTALLED and the DLL will stop being called.
// Use this method as much as possible, because user enumeration
// for MigrateUser9x is relatively slow. However, don't spend too
// much time here because QueryVersion is expected to run quickly.
//
return ERROR_NOT_INSTALLED;
}

//
// Screen saver is enabled, so tell Setup who we are. ProductID is used
// for display, so it must be localized. The ProductID string is
// converted to UNICODE for use on Windows NT via the MultiByteToWideChar
// Win32 API. The first element of CodePageArray is used to specify
// the code page of ProductID, and if no elements are returned in
// CodePageArray, Setup assumes CP_ACP.
//

*ProductID = g_MyProductId;

//
// Report our version. Zero is reserved for use by DLLs that
// ship with Windows NT.
//

*DllVersion = 1;

//
// Because we have English messages, we return an array that has
// the English language ID. The sublanguage is neutral because
// we do not have currency, time, or other geographic-specific
// information in our messages.
//
// Tip: If it makes more sense for your DLL to use locales,
// return ERROR_NOT_INSTALLED if the DLL detects that an appropriate
// locale is not installed on the machine.
//

*CodePageArray = g_CodePageArray;

//
// ExeNamesBuf - we pass a list of file names (the long versions)
// and let Setup find them for us. Keep this list short because
// every instance of the file on every hard drive will be reported
// in migrate.inf.
//
// Most applications don't need this behavior, because the registry
// usually contains full paths to installed components. We need it,
// though, because there are no registry settings that give us the
// paths of the screen saver DLLs.
//

*ExeNamesBuf = g_ExeNamesBuf;

return ERROR_SUCCESS;
}


LONG
CALLBACK
Initialize9x (
IN LPCSTR WorkingDirectory,
IN LPCSTR SourceDirectories,
LPVOID Reserved
)
{
INT Len;

//
// Because we returned ERROR_SUCCESS in QueryVersion, we are being
// called for initialization. Therefore, we know screen savers are
// enabled on the machine at this point.
//

//
// Make global copies of WorkingDirectory and SourceDirectories --
// we will not get this information again, and we shouldn't
// count on Setup keeping the pointer valid for the life of our
// DLL.
//

Len = CountStringBytes (WorkingDirectory);
g_WorkingDirectory = HeapAlloc (g_hHeap, 0, Len);

if (!g_WorkingDirectory) {
return GetLastError();
}

CopyMemory (g_WorkingDirectory, WorkingDirectory, Len);

Len = CountMultiStringBytes (SourceDirectories);
g_SourceDirectories = HeapAlloc (g_hHeap, 0, Len);

if (!g_SourceDirectories) {
return GetLastError();
}

CopyMemory (g_SourceDirectories, SourceDirectories, Len);

//
// Now create our private 'settings file' path
//

GenerateFilePaths();

//
// Return success to have MigrateUser9x called
//
// Tip: A DLL can save system settings during Initialize9x as
// well as MigrateSystem9x.
//
//

return ERROR_SUCCESS;
}


LONG
CALLBACK
MigrateUser9x (
IN HWND ParentWnd,
IN LPCSTR UnattendFile,
IN HKEY UserRegKey,
IN LPCSTR UserName,
LPVOID Reserved
)
{
HKEY RegKey;
LPCSTR ScrnSaveExe;
DWORD rc = ERROR_SUCCESS;
LPSTR SectionNameBuf, p;
DWORD SectionNameSize;
DWORD Len;

//
// This DLL does not require input from the user to upgrade
// their settings, so ParentWnd is not used. Avoid displaying
// any user interface when possible.
//
// We don't need to use UnattendFile settings because we are not
// a service (such as a network redirector). Therefore, we do not
// use the UnattendFile parameter.
//
// We don't have any files that need to be generated or expanded on
// the NT side of Setup, so we do not write to the
// [NT Disk Space Requirements] section of migrate.inf.
//

//
// We must collect a few registry keys:
//
// HKCU\Control Panel\Desktop
// ScreenSaveActive
// ScreenSaveLowPowerActive
// ScreenSaveLowPowerTimeout
// ScreenSavePowerOffActive
// ScreenSavePowerOffTimeout
// ScreenSaveTimeOut
// ScreenSaveUsePassword
//
// If ScreenSave_Data exists, we tell the user that their
// password is not supported by writing an incompatiility
// message.
//

//
// Save the user name in a global so our utils write to the
// correct section.
//

if (UserName) {
g_User = UserName;
} else {
g_User = S_DEFAULT_USER;
}

// OpenRegKey is our utility (in utils.c)
RegKey = OpenRegKey (UserRegKey, REGKEY_DESKTOP);
if (!RegKey) {
//
// User's registry is invalid, so skip the user
//
return ERROR_NOT_INSTALLED;
}

//
// Note: NO changes allowed on Win9x side, we can only read our
// settings and save them in a file.
//

if (!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEACTIVE) ||
!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVELOWPOWERACTIVE) ||
!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVELOWPOWERTIMEOUT) ||
!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEPOWEROFFACTIVE) ||
!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEPOWEROFFTIMEOUT) ||
!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVETIMEOUT) ||
!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEUSEPASSWORD)
) {
rc = GetLastError();
}

if (atoi (GetRegValueString (RegKey, REGVAL_SCREENSAVEUSEPASSWORD))) {
// Queue change so there is only one message
g_FoundPassword = TRUE;
}

//
// Save EXE location in our dat file
//

ScrnSaveExe = GetScrnSaveExe();

if (ScrnSaveExe) {
if (!SaveDatFileKeyAndVal (S_SCRNSAVE_EXE, ScrnSaveExe)) {
rc = GetLastError();
}
}

//
// Copy control.ini sections to our dat file
//

SectionNameSize = 32768;
SectionNameBuf = (LPSTR) HeapAlloc (g_hHeap, 0, SectionNameSize);
if (!SectionNameBuf) {
return GetLastError();
}

GetPrivateProfileString (
NULL,
NULL,
S_DOUBLE_EMPTY,
SectionNameBuf,
SectionNameSize,
S_CONTROL_INI
);

Len = _mbslen (S_SCRNSAVE_DOT);
for (p = SectionNameBuf ; *p ; p = _mbschr (p, 0) + 1) {
//
// Determine if section name has "Screen Saver." at the beginning
//

if (!_mbsnicmp (p, S_SCRNSAVE_DOT, Len)) {
//
// It does, so save it to our private file
//
SaveControlIniSection (p, p + Len);
}
}

CloseRegKey (RegKey);

if (rc != ERROR_SUCCESS) {
CHAR Msg[512];

wsprintf (Msg, USER_PROCESSING_ERROR, g_User, rc);
SetupLogError (Msg, LogSevError);
} else {
//
// Write handled for every setting we are processing. Because this
// DLL supports only some of the values in the Desktop key, we must
// be very specific as to which values are actually handled. If
// your DLL handles all registry values AND subkeys of a registry
// key, you can specify NULL in the second parameter of
// MigInf_AddHandledRegistry.
//

MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEACTIVE);
MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVELOWPOWERACTIVE);
MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVELOWPOWERTIMEOUT);
MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEPOWEROFFACTIVE);
MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEPOWEROFFTIMEOUT);
MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVETIMEOUT);
MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEUSEPASSWORD);
}

return rc;
}


LONG
CALLBACK

MigrateSystem9x (
IN HWND ParentWnd,
IN LPCSTR UnattendFile,
LPVOID Reserved
)
{
//
// We handle the password provider incompatibility
//

MigInf_AddHandledRegistry (FULL_REGKEY_PWD_PROVIDER, NULL);

//
// Write incompatibility message if necessary (detected in MigrateUser9x)
//

if (g_FoundPassword) {
MigInf_AddMessage (g_MyProductId, PASSWORD_ALERT);
MigInf_AddMessageRegistry (
g_MyProductId,
FULL_REGKEY_DESKTOP,
REGVAL_SCREENSAVEUSEPASSWORD
);
}

//
// Write memory version of migrate.inf to disk
//

if (!MigInf_WriteInfToDisk()) {
return GetLastError();
}

return ERROR_SUCCESS;
}


LONG
CALLBACK
InitializeNT (
IN LPCWSTR WorkingDirectory,
IN LPCWSTR SourceDirectories,
LPVOID Reserved
)
{
INT Length;
LPCWSTR p;

//
// Save our working directory and source directory. We
// convert UNICODE to ANSI because we know we are on a US
// version of NT. This conversion is not valid for far east
// languages, because we use CP_ACP (ANSI code page).
//
// If your DLL supports far east languages, use UNICODE and
// call the W version of Win32 functions that take strings.
//

//
// Compute length of source directories
//

p = SourceDirectories;
while (*p) {
p = wcschr (p, 0) + 1;
}
p++;
Length = (p - SourceDirectories) / sizeof (WCHAR);

//
// Convert the directories from UNICODE to DBCS. This DLL is
// compiled in ANSI.
//

g_WorkingDirectory = (LPSTR) HeapAlloc (g_hHeap, 0, MAX_PATH);
if (!g_WorkingDirectory) {
return GetLastError();
}

WideCharToMultiByte (
CP_ACP,
0,
WorkingDirectory,
-1,
g_WorkingDirectory,
MAX_PATH,
NULL,
NULL
);

g_SourceDirectories = (LPSTR) HeapAlloc (g_hHeap, 0, Length * sizeof(WCHAR));
if (!g_SourceDirectories) {
return GetLastError();
}

WideCharToMultiByte (
CP_ACP,
0,
SourceDirectories,
Length,
g_SourceDirectories,
Length * sizeof (WCHAR),
NULL,
NULL
);

//
// Now generate the derived file names
//

GenerateFilePaths();

//
// Note: We have no use for g_SourceDirectories for the screen saver
// upgrade. The g_SourceDirectories string points to the Windows
// NT media (i.e. e:\i386) and optional directories specified on
// the WINNT32 command line.
//

return ERROR_SUCCESS;
}



LONG
CALLBACK
MigrateUserNT (
IN HINF UnattendInfHandle,
IN HKEY UserRegKey,
IN LPCWSTR UserName,
LPVOID Reserved
)
{
HKEY DesktopRegKey;
DWORD rc = ERROR_SUCCESS;
BOOL b = TRUE;

//
// Setup gives us the UnattendInfHandle instead of the file name,
// so we don't have to open the inf file repeatitively. Since
// Setup opened the handle, let Setup close it.
//

//
// Convert UserName to ANSI
//

if (UserName) {
WideCharToMultiByte (
CP_ACP,
0,
UserName,
-1,
g_UserNameBuf,
MAX_PATH,
NULL,
NULL
);

g_User = g_UserNameBuf;
} else {
g_User = S_DEFAULT_USER;
}

//
// Setup copies all of the Win9x registry, EXCEPT for the registry
// keys that are suppressed in usermig.inf or wkstamig.inf.
//
// We need the HKCU\Control Panel\Desktop key, and because this is
// an OS key, the settings have been altered. Most applications
// store their settings in HKCU\Software, HKLM\Software or
// HKCC\Software, and all three of these keys are copied in their
// entirety (except the operating system settings in
// Software\Microsoft\Windows).
//
// When the non-OS software settings are copied from Win9x to NT, Setup
// sometimes alters their value. For example, all registry values
// that point to a file that was moved from SYSTEM to SYSTEM32
// are modified to point to the right place.
//

//
// Note: we use CreateRegKey here, but actually the key always exists
// because the NT defaults have been copied into the user's registry
// already. This approach reduces the possibility of failure.
//

DesktopRegKey = CreateRegKey (UserRegKey, REGKEY_DESKTOP);
if (!DesktopRegKey) {
CHAR Msg[512];

rc = GetLastError();
wsprintf (Msg, S_REGISTRY_ERROR, g_User, rc);
SetupLogError (Msg, LogSevError);

return rc;
}

// The variable b is used to fall through when we fail unexpectedly

b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVEACTIVE,
NULL
);

if (b) {
b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVELOWPOWERACTIVE,
REGVAL_LOWPOWERACTIVE
);
}
if (b) {
b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVELOWPOWERTIMEOUT,
REGVAL_LOWPOWERTIMEOUT
);
}
if (b) {
b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVEPOWEROFFACTIVE,
REGVAL_POWEROFFACTIVE
);
}
if (b) {
b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVEPOWEROFFTIMEOUT,
REGVAL_POWEROFFTIMEOUT
);
}
if (b) {
b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVETIMEOUT,
NULL
);
}
if (b) {
b = TranslateGeneralSetting (
DesktopRegKey,
REGVAL_SCREENSAVEUSEPASSWORD,
REGVAL_SCREENSAVERISSECURE
);
}

if (b) {
b = SaveScrName (DesktopRegKey, S_SCRNSAVE_EXE);
}

if (b) {
//
// For screen savers work differently on Win9x and NT, perform
// translation.
//

TranslateScreenSavers (UserRegKey);

//
// The other settings just need to be copied from control.ini
// to the registry.
//

CopyUntranslatedSettings (UserRegKey);
}


CloseRegKey (DesktopRegKey);

//
// Always return success, because if an error occurred for one user,
// we don't have a reason not to process the next user. If your DLL
// runs into a fatal problem, such as a disk space shortage, you
// should return the error.
//

return ERROR_SUCCESS;
}


LONG
CALLBACK
MigrateSystemNT (
IN HINF UnattendInfHandle,
LPVOID Reserved
)
{
CHAR FileName[MAX_PATH];
HINF MigrateInf;
INFCONTEXT ic;
CHAR Msg[512];

//
// We now delete the Win9x screen savers that were replaced
// by Windows NT.
//

MigrateInf = SetupOpenInfFile (
g_MigrateDotInf,
NULL,
INF_STYLE_WIN4,
NULL
);

if (MigrateInf != INVALID_HANDLE_VALUE) {

//
// Use Setup APIs to scan migration paths section
//

if (SetupFindFirstLine (MigrateInf, S_MIGRATION_PATHS, NULL, &ic)) {
do {
if (SetupGetStringField (&ic, 0, FileName, MAX_PATH, NULL)) {
//
// All 32-bit binaries located in the Win9x system directory
// were moved to system32. However, the paths reported in
// [Migration Paths] are the original Win9x paths. We
// convert c:\windows\system\file to c:\windows\system32\file.
//

ConvertSystemToSystem32 (FileName);

//
// Now delete the file. Ignore errors because user may have
// lost power, and we may be going through this a second time.
//

if (!DeleteFile (FileName)) {
wsprintf (Msg, DELETEFILE_ERROR, FileName, GetLastError());
SetupLogError (Msg, LogSevError);
} else {
wsprintf (Msg, DELETEFILE_SUCCESS, FileName);
SetupLogError (Msg, LogSevInformation);
}
}
} while (SetupFindNextLine (&ic, &ic));
}

SetupCloseInfFile (MigrateInf);
}

return ERROR_SUCCESS;
}