{*******************************************************} { } { Tocsg.Registry } { } { Copyright (C) 2022 kkuzil } { } {*******************************************************} unit Tocsg.Registry; interface uses WinApi.Windows, System.Classes, System.Win.Registry, System.SysUtils, System.Generics.Collections, Tocsg.Trace; const REG_RUN = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run\'; REG_PROFILE = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\'; type TRkInfo = record sKName: string; ftLastWriteTime: TFileTime; end; TRkInfoList = TList; function SetRegValueString(K: HKEY; const sKey, sValueName, sValue: String; bCanCreate: Boolean = false): Boolean; function SetRegValueStringEx(K: HKEY; const sKey, sValueName, sValue: String; bCanCreate: Boolean = false): Boolean; function SetRegValueInteger(K: HKEY; const sKey, sValueName: String; nValue: Integer; bCanCreate: Boolean = false): Boolean; function ExistsKey(K: HKEY; const sKey: String): Boolean; function CountRegKeyValue(K: HKEY; const sKey: String): Integer; function GetRegValueAsString(K: HKEY; const sKey, sValue: String; bCreate: Boolean = false): String; function GetRegValueAsInteger(K: HKEY; const sKey, sValue: String; nDefVal: Integer = 0): Integer; function GetRegRecentUserSid: String; function DelRegKey(K: HKEY; const sKey: String): Boolean; function DelRegValue(K: HKEY; const sKey, sValueName: String): Boolean; function ExtRegSubKeyToStrings(K: HKEY; sKey: String; aStrings: TStrings): Boolean; function ExistsRunAppByHLM(const AppName: String; aRootKey: HKEY = HKEY_LOCAL_MACHINE): Boolean; function AddRunAppByHLM(const AppName, sValue: String; aRootKey: HKEY = HKEY_LOCAL_MACHINE): Boolean; function DeleteRunAppByHLM(const AppName: String; aRootKey: HKEY = HKEY_LOCAL_MACHINE): Boolean; function GetDomainUserNameFromReg: String; function GetUserNameFromReg(sRegKey: String = ''): String; procedure CopyRegKey(K: HKEY; sSrcKey, sDestKey: String); function ExtrSubKeySortList(K: HKEY; sKey: String; aRkInfoList: TRkInfoList): Integer; type // 기본 TRegistry 클래스는 REG_MULTI_SZ 타입까지 분석하지 못한다. // 그래서 헬퍼 클래스로 지원되도록 추가함 14_1006 15:08:25 kku TRegDataTypeEx = (rdxUnknown, rdxString, rdxExpandString, rdxInteger, rdxBinary, rdxMultiString{추가}); TRegDataInfoEx = record RegData: TRegDataTypeEx; DataSize: Integer; end; TRegistryHelper = class helper for TRegistry private function GetDataEx(const Name: string; Buffer: Pointer; BufSize: Integer; var RegData: TRegDataTypeEx): Integer; // 지금까지 외부에서 사용할일이 없어서 숨김 14_1006 15:09:49 kku function GetDataInfoEx(const ValueName: string; var Value: TRegDataInfoEx): Boolean; // 지금까지 외부에서 사용할일이 없어서 숨김 14_1006 15:09:49 kku public function GetDataAsStringEx(const ValueName: string; PrefixType: Boolean = false): string; function GetDataTypeEx(const ValueName: string): TRegDataTypeEx; // 외부에서는 이걸로 타입 확인하고, GetDataAsStringEx 이걸로 데이터 가져오는걸로 사용한다 14_1006 15:09:20 kku procedure WriteMultiString(const ValueName: String; aMultiStrList: TStrings); end; implementation uses System.RTLConsts, Tocsg.Safe, Tocsg.Exception, Tocsg.DateTime, Tocsg.Process, System.Generics.Defaults; function CompareByLastWriteTime(const Left, Right: TRkInfo): Integer; begin Result := CompareFileTime(Right.ftLastWriteTime, Left.ftLastWriteTime); end; function SetRegValueString(K: HKEY; const sKey, sValueName, sValue: String; bCanCreate: Boolean = false): Boolean; var Reg: TRegistry; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := K; if Reg.OpenKey(sKey, bCanCreate) then if bCanCreate or Reg.ValueExists(sValueName) then begin try Reg.WriteString(sValueName, sValue); Result := true; except end; end; end; function SetRegValueStringEx(K: HKEY; const sKey, sValueName, sValue: String; bCanCreate: Boolean = false): Boolean; var Reg: TRegistry; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := K; if Reg.OpenKey(sKey, bCanCreate) then if bCanCreate or Reg.ValueExists(sValueName) then begin try Reg.WriteExpandString(sValueName, sValue); Result := true; except end; end; end; function SetRegValueInteger(K: HKEY; const sKey, sValueName: String; nValue: Integer; bCanCreate: Boolean = false): Boolean; var Reg: TRegistry; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := K; if Reg.OpenKey(sKey, bCanCreate) then if bCanCreate or Reg.ValueExists(sValueName) then begin try Reg.WriteInteger(sValueName, nValue); Result := true; except end; end; end; function ExistsKey(K: HKEY; const sKey: String): Boolean; var Reg: TRegistry; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := K; try Result := Reg.KeyExists(sKey); except // end; end; function CountRegKeyValue(K: HKEY; const sKey: String): Integer; var Reg: TRegistry; StrList: TStringList; begin Result := 0; Guard(Reg, TRegistry.Create); Reg.RootKey := K; try if Reg.OpenKey(sKey, false) then begin Guard(StrList, TStringList.Create); Reg.GetValueNames(StrList); Result := StrList.Count; end; except // end; end; function GetRegValueAsString(K: HKEY; const sKey, sValue: String; bCreate: Boolean = false): String; var Reg: TRegistry; begin Result := ''; Guard(Reg, TRegistry.Create); Reg.RootKey := K; try if Reg.OpenKey(sKey, bCreate) and Reg.ValueExists(sValue) then Result := Reg.ReadString(sValue); except // end; end; function GetRegValueAsInteger(K: HKEY; const sKey, sValue: String; nDefVal: Integer = 0): Integer; var Reg: TRegistry; begin Result := nDefVal; Guard(Reg, TRegistry.Create); Reg.RootKey := K; try if Reg.OpenKey(sKey, false) and Reg.ValueExists(sValue) then begin Result := Reg.ReadInteger(sValue); end; except // end; end; function GetRegRecentUserSid: String; var Reg, RegCheck: TRegistry; StrList: TStringList; i, nHigh, nLow: Integer; sTemp: String; dtRecent, dtCheck: TDateTime; begin Result := ''; try Guard(Reg, TRegistry.Create); Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKeyReadOnly(REG_PROFILE) then exit; Guard(StrList, TStringList.Create); dtRecent := 0; Reg.GetKeyNames(StrList); Reg.CloseKey; Guard(RegCheck, TRegistry.Create); RegCheck.RootKey := HKEY_USERS; for i := 0 to StrList.Count - 1 do begin if (Length(StrList[i]) > 20) and RegCheck.OpenKeyReadOnly(StrList[i]) then // 키가 존재하는거 대상으로 함 (로그온된거) 22_0620 16:50:47 kku begin RegCheck.CloseKey; // LocalProfileLoadTimeHigh, LocalProfileLoadTimeLow가 0인 경우가 있다... 22_0620 16:45:02 kku nHigh := GetRegValueAsInteger(HKEY_LOCAL_MACHINE, REG_PROFILE + StrList[i], 'LocalProfileLoadTimeHigh'); nLow := GetRegValueAsInteger(HKEY_LOCAL_MACHINE, REG_PROFILE + StrList[i], 'LocalProfileLoadTimeLow'); sTemp := IntToHex(nHigh) + IntToHex(nLow); // UTC-0 dtCheck := ConvTimestampToDateTime(LONGLONG(StrToInt64Def('$' + sTemp, 0))); if (dtRecent = 0) or (dtRecent < dtCheck) then begin dtRecent := dtCheck; Result := StrList[i]; end; end; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetRegRecentUserSid()'); end; end; function DelRegKey(K: HKEY; const sKey: String): Boolean; var Reg: TRegistry; begin Guard(Reg, TRegistry.Create); Reg.RootKey := K; Result := Reg.DeleteKey(sKey); end; function DelRegValue(K: HKEY; const sKey, sValueName: String): Boolean; var Reg: TRegistry; begin Guard(Reg, TRegistry.Create); Reg.RootKey := K; Result := false; if Reg.OpenKey(sKey, false) then Result := Reg.DeleteValue(sValueName); end; function ExtRegSubKeyToStrings(K: HKEY; sKey: String; aStrings: TStrings): Boolean; var Reg: TRegistry; SubList: TStringList; i: Integer; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := K; if Reg.KeyExists(sKey) then if Reg.OpenKey(sKey, false) then begin sKey := IncludeTrailingPathDelimiter(sKey); Guard(SubList, TStringList.Create); Reg.GetKeyNames(SubList); for i := 0 to SubList.Count - 1 do begin // 값까지 체크 해준다. notepad++ 같은 경우 설정값을 여기에 남겨 놓는경우가 있음 22_0614 16:20:37 kku if GetRegValueAsString(K, sKey + SubList[i], 'DisplayName') <> '' then aStrings.Add(sKey + SubList[i]); end; Result := true; end; end; function ExistsRunAppByHLM(const AppName: String; aRootKey: HKEY = HKEY_LOCAL_MACHINE): Boolean; var Reg: TRegistry; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := aRootKey; if Reg.OpenKey(REG_RUN, false) then Result := Reg.ValueExists(AppName); end; function AddRunAppByHLM(const AppName, sValue: String; aRootKey: HKEY = HKEY_LOCAL_MACHINE): Boolean; var Reg: TRegistry; begin Result := false; Guard(Reg, TRegistry.Create); Reg.RootKey := aRootKey; if Reg.OpenKey(REG_RUN, false) then begin try Reg.WriteString(AppName, sValue); Result := true; except // reg write 오류? end; end; end; function DeleteRunAppByHLM(const AppName: String; aRootKey: HKEY = HKEY_LOCAL_MACHINE): Boolean; begin Result := DelRegValue(aRootKey, REG_RUN, AppName); end; { TRegistryHelper } function DataTypeToRegDataEx(Value: Integer): TRegDataTypeEx; begin case Value of REG_SZ : Result := rdxString; REG_EXPAND_SZ : Result := rdxExpandString; REG_MULTI_SZ : Result := rdxMultiString; REG_DWORD : Result := rdxInteger; REG_BINARY : Result := rdxBinary; else Result := rdxUnknown; end; end; function BinaryToHexString(const BinaryData: array of Byte; const PrefixStr: string): string; var DataSize, I, Offset: Integer; HexData: string; PResult: PChar; begin OffSet := 0; if PrefixStr <> '' then begin Result := PrefixStr; Inc(Offset, Length(PrefixStr)); end; DataSize := Length(BinaryData); SetLength(Result, Offset + (DataSize*3) - 1); // less one for last ',' PResult := PChar(Result); // Use a char pointer to reduce string overhead for I := 0 to DataSize - 1 do begin HexData := IntToHex(BinaryData[I], 2); PResult[Offset] := HexData[1]; PResult[Offset+1] := HexData[2]; if I < DataSize - 1 then PResult[Offset+2] := ','; Inc(Offset, 3); end; end; function TRegistryHelper.GetDataInfoEx(const ValueName: string; var Value: TRegDataInfoEx): Boolean; var DataType: Integer; begin FillChar(Value, SizeOf(TRegDataInfo), 0); Result := CheckResult(RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, nil, @Value.DataSize)); Value.RegData := DataTypeToRegDataEx(DataType); end; function TRegistryHelper.GetDataTypeEx(const ValueName: string): TRegDataTypeEx; var Info: TRegDataInfoEx; begin if GetDataInfoEx(ValueName, Info) then Result := Info.RegData else Result := rdxUnknown; end; function TRegistryHelper.GetDataEx(const Name: string; Buffer: Pointer; BufSize: Integer; var RegData: TRegDataTypeEx): Integer; var DataType: Integer; begin DataType := REG_NONE; if not CheckResult(RegQueryValueEx(CurrentKey, PChar(Name), nil, @DataType, PByte(Buffer), @BufSize)) then raise ERegistryException.CreateResFmt(@SRegGetDataFailed, [Name]); Result := BufSize; RegData := DataTypeToRegDataEx(DataType); end; function TRegistryHelper.GetDataAsStringEx(const ValueName: string; PrefixType: Boolean = false): string; const SDWORD_PREFIX = 'dword:'; SHEX_PREFIX = 'hex:'; var Info: TRegDataInfoEx; BinaryBuffer: array of Byte; nPos: Integer; pBuf: TBytes; begin Result := ''; if GetDataInfoEx(ValueName, Info) and (Info.DataSize > 0) then begin case Info.RegData of rdxString, rdxExpandString: begin SetString(Result, nil, Info.DataSize); GetDataEx(ValueName, PChar(Result), Info.DataSize, Info.RegData); SetLength(Result, StrLen(PChar(Result))); end; rdxMultiString : begin SetLength(pBuf, Info.DataSize); GetDataEx(ValueName, pBuf, Info.DataSize, Info.RegData); nPos := 0; while nPos < Info.DataSize do begin Result := Result + PChar(@pBuf[nPos]); {$IFDEF UNICODE} Inc(nPos, (Length(PChar(@pBuf[nPos])) + 1) * 2); {$ELSE} Inc(nPos, Length(PChar(@pBuf[nPos])) + 1); {$ENDIF} if nPos < Info.DataSize then Result := Result + #13#10; end; end; rdxInteger: begin if PrefixType then Result := SDWORD_PREFIX+IntToHex(ReadInteger(ValueName), 8) else Result := IntToStr(ReadInteger(ValueName)); end; rdxBinary, rdxUnknown: begin SetLength(BinaryBuffer, Info.DataSize); ReadBinaryData(ValueName, Pointer(BinaryBuffer)^, Info.DataSize); if PrefixType then Result := BinaryToHexString(BinaryBuffer, SHEX_PREFIX) else Result := BinaryToHexString(BinaryBuffer, ''); end; end; end; end; procedure TRegistryHelper.WriteMultiString(const ValueName: String; aMultiStrList: TStrings); var i, nSize: Integer; pBuf: TBytes; begin nSize := 0; for i := 0 to aMultiStrList.Count - 1 do begin {$IFDEF UNICODE} Inc(nSize, (Length(aMultiStrList[i]) + 1) * 2); {$ELSE} Inc(nSize, Length(aMultiStrList[i]) + 1); {$ENDIF} end; SetLength(pBuf, nSize); ZeroMemory(pBuf, nSize); nSize := 0; for i := 0 to aMultiStrList.Count - 1 do begin StrCopy(PChar(@pBuf[nSize]), PChar(aMultiStrList[i])); {$IFDEF UNICODE} Inc(nSize, (Length(aMultiStrList[i]) + 1) * 2); {$ELSE} Inc(nSize, Length(aMultiStrList[i]) + 1); {$ENDIF} end; RegSetValueEx(CurrentKey, PChar(ValueName), 0, REG_MULTI_SZ, @pBuf[0], nSize); end; function GetDomainUserNameFromReg: String; var sRegKey, sDomain, sUser: String; begin Result := ''; try sRegKey := GetRegRecentUserSid; if sRegKey = '' then exit; sDomain := GetRegValueAsString(HKEY_USERS, sRegKey + '\Volatile Environment', 'USERDOMAIN'); sUser := GetRegValueAsString(HKEY_USERS, sRegKey + '\Volatile Environment', 'USERNAME'); if (sDomain <> '') and (sUser <> '') then Result := sDomain + '\' + sUser; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetDomainUserNameFromReg()'); end; end; function GetUserNameFromReg(sRegKey: String = ''): String; begin Result := ''; try if sRegKey = '' then sRegKey := GetRegRecentUserSid; if sRegKey = '' then exit; Result := GetRegValueAsString(HKEY_LOCAL_MACHINE, REG_PROFILE + sRegKey, 'ProfileImagePath'); if Result <> '' then Result := Trim(ExtractFileName(ExcludeTrailingPathDelimiter(Result))); except on E: Exception do ETgException.TraceException(E, 'Fail .. GetUserNameFromReg()'); end; end; procedure CopyRegKey(K: HKEY; sSrcKey, sDestKey: String); var Reg: TRegistry; begin try Guard(Reg, TRegistry.Create); Reg.RootKey := K; Reg.MoveKey(sSrcKey, sDestKey, false); except on E: Exception do ETgException.TraceException(E, 'Fail .. CopyRegKey()'); end; end; function ExtrSubKeySortList(K: HKEY; sKey: String; aRkInfoList: TRkInfoList): Integer; var Reg: TRegistry; SubList: TStringList; ftLocal, ftKey: TFileTime; RkInfo: TRkInfo; i: Integer; begin Result := 0; try Guard(Reg, TRegistry.Create); Reg.RootKey := HKEY_USERS; sKey := IncludeTrailingPathDelimiter(sKey); if Reg.OpenKeyReadOnly(sKey) then begin Guard(SubList, TStringList.Create); Reg.GetKeyNames(SubList); Reg.CloseKey; if SubList.Count > 0 then begin for i := 0 to SubList.Count - 1 do begin if Reg.OpenKeyReadOnly(sKey + SubList[i]) then begin if RegQueryInfoKey(Reg.CurrentKey, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, @ftKey) = ERROR_SUCCESS then begin FileTimeToLocalFileTime(ftKey, ftLocal); RkInfo.sKName := SubList[i]; RkInfo.ftLastWriteTime := ftLocal; aRkInfoList.Add(RkInfo); end; Reg.CloseKey; end; end; Result := aRkInfoList.Count; if Result > 0 then aRkInfoList.Sort(TComparer.Construct(CompareByLastWriteTime)); end; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. ExtrSubKeySortList()'); end; end; end.