{*******************************************************} { } { ThdInstMon } { } { Copyright (C) 2022 kkuzil } { } {*******************************************************} unit ThdInstMon; interface uses Tocsg.Thread, System.SysUtils, System.Classes, Winapi.Windows, Winapi.Messages; const REGKEY_UNINSTALL = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'; REGKEY_UNINSTALL_WOW64 = 'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'; IGNORE_APPS = 'MICROSOFT|Sophos'; IGNORE_DEL_APPS = 'MYSQL'; WM_INST_NOTIFY = WM_USER + 9481; type TThdInstMon = class(TTgThread) private bIsWorking_: Boolean; IgrAppList_, IgrDelAppList_: TStringList; protected hRcvWnd_: HWND; procedure Execute; override; public Constructor Create(hRcvWnd: HWND = 0); Destructor Destroy; override; // for HE procedure StartService; procedure StopService; end; implementation uses {$IFDEF _HE_} ManagerService, GlobalDefine, ManagerModel, {$ELSE} {$ENDIF} Tocsg.Registry, System.Win.Registry, Tocsg.Safe, Tocsg.Exception, Tocsg.Shell, Tocsg.Strings, Tocsg.Process, Tocsg.Files, Tocsg.Path, ParserLinkFile; { TThdInstMon } Constructor TThdInstMon.Create(hRcvWnd: HWND = 0); begin Inherited Create; bIsWorking_ := false; hRcvWnd_ := hRcvWnd; IgrAppList_ := TStringList.Create; SplitString(UpperCase(IGNORE_APPS), '|', IgrAppList_); IgrDelAppList_ := TStringList.Create; SplitString(UpperCase(IGNORE_DEL_APPS), '|', IgrDelAppList_); end; Destructor TThdInstMon.Destroy; begin FreeAndNil(IgrDelAppList_); FreeAndNil(IgrAppList_); Inherited; end; procedure TThdInstMon.StartService; begin if not bIsWorking_ then begin bIsWorking_ := true; StartThread; end; end; procedure TThdInstMon.StopService; begin if bIsWorking_ then begin bIsWorking_ := false; PauseThread; end; end; procedure TThdInstMon.Execute; function GetInstDirAndExeFiles(aReg: TRegistry; var sInstDir: String; var bBlock: Boolean; aExeList: TStrings): Boolean; var sVal: String; i: Integer; begin Result := false; sInstDir := ''; bBlock := true; aExeList.Clear; if aReg.ValueExists('InstallDir') then sVal := aReg.ReadString('InstallDir') else if aReg.ValueExists('InstallLocation') then sVal := aReg.ReadString('InstallLocation') else if aReg.ValueExists('UninstallString') then begin sVal := aReg.ReadString('UninstallString'); sVal := StringReplace(sVal, '"', '', [rfReplaceAll]); sVal := ExtractFilePath(sVal); end else exit; sVal := StringReplace(sVal, '"', '', [rfReplaceAll]); sInstDir := IncludeTrailingPathDelimiter(sVal); ExtrFilesFromDir(sInstDir, aExeList, true, 'EXE'); if aReg.ValueExists('DisplayName') then sVal := aReg.ReadString('DisplayName') else sVal := sInstDir; {$IFDEF _HE_} gMgSvc.SendEventLog(URI_USER_ACTION, LOGCODE_APP_INSTALLATION, Format('"%s" Installed', [sVal])); var AppInstKind: TAppInstKind := gMgSvc.ModePolicy.AppInstKind; case AppInstKind of aikBlock, aikPopup : begin var bIgr := false; var cChk := UpperCase(sVal); for i := 0 to IgrAppList_.Count - 1 do begin if cChk.Contains(IgrAppList_[i]) then // MICROSOFT 제외 추가 22_0816 17:58:23 kku begin bIgr := true; break; end; end; if not bIgr then begin if AppInstKind = aikBlock then begin for i := 0 to IgrDelAppList_.Count - 1 do begin if cChk.Contains(IgrDelAppList_[i]) then // MICROSOFT 제외 추가 22_0816 17:58:23 kku begin bBlock := false; break; end; end; if bBlock then sVal := sVal + '|PV'; end; gMgSvc.PopupMessage(TYPE_MSG_PREVENT_INSTALL, sVal); end; end; end; {$ENDIF} if hRcvWnd_ <> 0 then SendMessage(hRcvWnd_, WM_INST_NOTIFY, 0, NativeInt(sVal)); Result := true; end; procedure TerminateExes(aExeList: TStrings); var i: Integer; begin for i := 0 to aExeList.Count - 1 do TerminateProcessByName(aExeList[i]); end; procedure DeleteInstDir(sDir: String; bIncludeSubDir: Boolean = true; bForceDel: Boolean = false); var wfd: TWin32FindData; hSc: THandle; sPath: String; bRetry: Boolean; Label LB_Retry; begin sDir := IncludeTrailingPathDelimiter(sDir); sPath := sDir + '*.*'; hSc := FindFirstFile(PChar(sPath), wfd); if hSc = INVALID_HANDLE_VALUE then exit; try Repeat if (String(wfd.cFileName) <> '.') and (String(wfd.cFileName) <> '..') then if ((wfd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0) then begin DeleteInstDir(sDir + wfd.cFileName, bIncludeSubDir, bForceDel); end else begin bRetry := false; LB_Retry : if bForceDel and ((wfd.dwFileAttributes and FILE_ATTRIBUTE_READONLY) <> 0) then begin // 읽기 전용 속성 해제 22_0510 10:28:28 kku SetFileAttributes(PChar(sDir + wfd.cFileName), FILE_ATTRIBUTE_NORMAL); end; if not DeleteFile(PChar(sDir + wfd.cFileName)) then begin if (GetFileExt(wfd.cFileName).ToUpper = 'EXE') and not bRetry then begin TerminateProcessByName(wfd.cFileName); Sleep(500); bRetry := true; goto LB_Retry; end; end; end; Until not FindNextFile(hSc, wfd); finally WinApi.Windows.FindClose(hSc); end; end; function RemoveInstall(K: HKEY; sKey: String): Boolean; var Reg: TRegistry; sVal, sInstDir: String; ExeList, StrList: TStringList; bBlock: Boolean; // {$IFNDEF _HE_} procedure DeleteLinkFileFromDir(sDir: String); var i: Integer; Plf: TParserLinkFile; begin sDir := IncludeTrailingBackslash(sDir); StrList.Clear; ExtrFilesFromDir(sDir, StrList, false, 'LNK'); Guard(Plf, TParserLinkFile.Create); for i := 0 to StrList.Count - 1 do begin if plf.LoadFromFile(sDir + StrList[i]) then begin if ExeList.IndexOf(ExtractFileName(GetLfiValueFromCaption(Plf.LfiEntList, 'Base Path'))) <> -1 then DeleteFile(PChar(sDir + StrList[i])); end; end; end; // {$ENDIF} begin Result := false; try Guard(Reg, TRegistry.Create); Reg.RootKey := K; if not Reg.OpenKey(sKey, false) then begin _Trace('Fail .. Execute() > RemoveInstall() > Reg.OpenKey(), Key="%s"', [sKey]); exit; end; Guard(ExeList, TStringList.Create); ExeList.CaseSensitive := false; if not GetInstDirAndExeFiles(Reg, sInstDir, bBlock, ExeList) then exit; {$IFDEF _HE_} // 주의 : HE에서는 차단을 안하니까 Result는 반드시 false로 해야한다. // 차단 기능 추가됨 22_1121 09:35:43 kku if not bBlock or (gMgSvc.ModePolicy.AppInstKind <> aikBlock) then exit; {$ENDIF} Guard(StrList, TStringList.Create); if Reg.ValueExists('QuietUninstallString') then begin sVal := Reg.ReadString('QuietUninstallString'); SplitString2(sVal, ' ', StrList); TerminateExes(ExeList); case StrList.Count of 2 : ExecutePath_hide(StrList[0], StrList[1]); 3 : ExecutePath_hide(StrList[0], StrList[1] + ' ' + StrList[2]); // 여기까지 안올거 같긴 하다.. else ExecutePath_hide(sVal); end; end else if Reg.ValueExists('UninstallString') then begin sVal := Reg.ReadString('UninstallString'); if Pos('msiexec.exe', LowerCase(sVal)) = 1 then begin TerminateExes(ExeList); ExecutePath_hide('MsiExec.exe', Format('/x %s /quiet /qn /norestart', [ExtractFileName(sKey)])); end else begin TerminateExes(ExeList); Sleep(500); end; end; if sInstDir <> '' then begin DeleteInstDir(sInstDir, true, true); DeleteDir(sInstDir, true, true); // todo : 인스톨러 때문에 설치 폴더가 잡혀있을때 지워주는 스레드에 등록? end; DeleteLinkFileFromDir(GetDesktopDir); DeleteLinkFileFromDir(GetCommonDesktopDir); Reg.CloseKey; Reg.DeleteKey(sKey); Result := true; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. Execute() > RemoveInstall()'); end; end; var {$IFDEF WIN64} PrevInstListHCU32, PrevInstListHLM32, NewInstListHCU32, NewInstListHLM32, {$ENDIF} PrevInstListHCU, PrevInstListHLM: TStringList; NewInstListHCU, NewInstListHLM: TStringList; i: Integer; sCurUserSid: String; begin sCurUserSid := GetRegRecentUserSid; {$IFDEF WIN64} Guard(PrevInstListHCU32, TStringList.Create); Guard(PrevInstListHLM32, TStringList.Create); Guard(NewInstListHCU32, TStringList.Create); Guard(NewInstListHLM32, TStringList.Create); {$ENDIF} Guard(PrevInstListHCU, TStringList.Create); Guard(PrevInstListHLM, TStringList.Create); Guard(NewInstListHCU, TStringList.Create); Guard(NewInstListHLM, TStringList.Create); while not Terminated and not GetWorkStop do begin try {$IFDEF WIN64} NewInstListHCU32.Clear; NewInstListHLM32.Clear; {$ENDIF} NewInstListHCU.Clear; NewInstListHLM.Clear; if sCurUserSid <> '' then ExtRegSubKeyToStrings(HKEY_USERS, sCurUserSid + '\' + REGKEY_UNINSTALL, NewInstListHCU); ExtRegSubKeyToStrings(HKEY_LOCAL_MACHINE, REGKEY_UNINSTALL, NewInstListHLM); {$IFDEF WIN64} if sCurUserSid <> '' then ExtRegSubKeyToStrings(HKEY_USERS, sCurUserSid + '\' + REGKEY_UNINSTALL_WOW64, NewInstListHCU32); ExtRegSubKeyToStrings(HKEY_LOCAL_MACHINE, REGKEY_UNINSTALL_WOW64, NewInstListHLM32); {$ENDIF} if ( {$IFDEF WIN64} NewInstListHCU32.Count + NewInstListHLM32.Count + {$ENDIF} NewInstListHCU.Count + NewInstListHLM.Count ) = 0 then begin Sleep(1000); continue; end; {$IFDEF WIN64} // HKEY_USERS 32 if PrevInstListHCU32.Count > 0 then begin for i := NewInstListHCU32.Count - 1 downto 0 do begin if (PrevInstListHCU32.Count > 0) and (PrevInstListHCU32.IndexOf(NewInstListHCU32[i]) = -1) then begin // 새로 설치된 프로그램 확인 if RemoveInstall(HKEY_USERS, NewInstListHCU32[i]) then NewInstListHCU32.Delete(i); end; end; PrevInstListHCU32.Clear; PrevInstListHCU32.AddStrings(NewInstListHCU32); end else PrevInstListHCU32.AddStrings(NewInstListHCU32); // HKEY_LOCAL_MACHINE 32 if PrevInstListHLM32.Count > 0 then begin for i := NewInstListHLM32.Count - 1 downto 0 do begin if Terminated or GetWorkStop then exit; if (PrevInstListHLM32.Count > 0) and (PrevInstListHLM32.IndexOf(NewInstListHLM32[i]) = -1) then begin // 새로 설치된 프로그램 확인 if RemoveInstall(HKEY_LOCAL_MACHINE, NewInstListHLM32[i]) then NewInstListHLM32.Delete(i); end; end; PrevInstListHLM32.Clear; PrevInstListHLM32.AddStrings(NewInstListHLM32); end else PrevInstListHLM32.AddStrings(NewInstListHLM32); {$ENDIF} // HKEY_USERS if PrevInstListHCU.Count > 0 then begin for i := NewInstListHCU.Count - 1 downto 0 do begin if (PrevInstListHCU.Count > 0) and (PrevInstListHCU.IndexOf(NewInstListHCU[i]) = -1) then begin // 새로 설치된 프로그램 확인 if RemoveInstall(HKEY_USERS, NewInstListHCU[i]) then NewInstListHCU.Delete(i); end; end; PrevInstListHCU.Clear; PrevInstListHCU.AddStrings(NewInstListHCU); end else PrevInstListHCU.AddStrings(NewInstListHCU); // HKEY_LOCAL_MACHINE if PrevInstListHLM.Count > 0 then begin for i := NewInstListHLM.Count - 1 downto 0 do begin if Terminated or GetWorkStop then exit; if (PrevInstListHLM.Count > 0) and (PrevInstListHLM.IndexOf(NewInstListHLM[i]) = -1) then begin // 새로 설치된 프로그램 확인 if RemoveInstall(HKEY_LOCAL_MACHINE, NewInstListHLM[i]) then NewInstListHLM.Delete(i); end; end; PrevInstListHLM.Clear; PrevInstListHLM.AddStrings(NewInstListHLM); end else PrevInstListHLM.AddStrings(NewInstListHLM); Sleep(1000); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. Execute()'); end; end; end; end.