diff --git a/Build/Release/x64/repacls.exe b/Build/Release/x64/repacls.exe index 8bcd820..7b160a8 100644 Binary files a/Build/Release/x64/repacls.exe and b/Build/Release/x64/repacls.exe differ diff --git a/Build/Release/x86/repacls.exe b/Build/Release/x86/repacls.exe index 4d90933..870571d 100644 Binary files a/Build/Release/x86/repacls.exe and b/Build/Release/x86/repacls.exe differ diff --git a/Build/Repacls.zip b/Build/Repacls.zip index a9bb011..d2e0f3c 100644 Binary files a/Build/Repacls.zip and b/Build/Repacls.zip differ diff --git a/Build/build.cmd b/Build/build.cmd index 8c61b42..c5545d1 100644 --- a/Build/build.cmd +++ b/Build/build.cmd @@ -4,7 +4,7 @@ CLS SET PATH=%WINDIR%\system32;%WINDIR%\system32\WindowsPowerShell\v1.0 :: cert info to use for signing -SET CERT=D4C06C609230B7BC433A428BFFD6EDC4F77FD166 +SET CERT=193A6FACBFBFC43ADB74ABB669543FCBC1C4F26C set TSAURL=http://time.certum.pl/ set LIBNAME=Repacls set LIBURL=https://github.com/NoMoreFood/Repacls diff --git a/Functions.h b/Functions.h index 69073dd..19fccfe 100644 --- a/Functions.h +++ b/Functions.h @@ -6,7 +6,7 @@ // helper functions VOID EnablePrivs(); -PSID GetSidFromName(std::wstring & sAccountName); +PSID GetSidFromName(const std::wstring & sAccountName); std::wstring GetNameFromSid(const PSID tSid, bool * bMarkAsOrphan = nullptr); std::wstring GetNameFromSidEx(const PSID tSid, bool * bMarkAsOrphan = nullptr); std::wstring GetDomainNameFromSid(const PSID tSid); @@ -14,8 +14,10 @@ std::wstring GenerateAccessMask(DWORD iCurrentMask); std::wstring GenerateInheritanceFlags(DWORD iCurrentFlags); HANDLE RegisterFileHandle(HANDLE hFile, const std::wstring & sOperation); std::wstring GetAntivirusStateDescription(); -std::wstring FileTimeToString(LPFILETIME const tFileTime); -BOOL WriteToFile(const std::wstring & sStringToWrite, HANDLE hFile); +std::wstring FileTimeToString(const FILETIME tFileTime); +std::wstring FileSizeToString(const LARGE_INTEGER iFileSize); +std::wstring FileAttributesToString(const DWORD iAttributes); +BOOL WriteToFile(const std::wstring & sStringToWrite, HANDLE hFile) noexcept; // helper typedefs typedef struct SidCompare diff --git a/Helpers.cpp b/Helpers.cpp index 3a0740a..ae45c4d 100644 --- a/Helpers.cpp +++ b/Helpers.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,7 +19,7 @@ #include "Operation.h" #include "Functions.h" -PSID GetSidFromName(std::wstring & sAccountName) +PSID GetSidFromName(const std::wstring & sAccountName) { // for caching static std::shared_mutex oMutex; @@ -449,11 +450,11 @@ std::wstring GetAntivirusStateDescription() return (bIsEnabled) ? L"On" : L"Off"; } -std::wstring FileTimeToString(LPFILETIME const tFileTime) +std::wstring FileTimeToString(const FILETIME tFileTime) { // the date format function require system time structure SYSTEMTIME tTime; - FileTimeToSystemTime(tFileTime, &tTime); + FileTimeToSystemTime(&tFileTime, &tTime); // convert the date to a string and return WCHAR sTime[24]; @@ -465,7 +466,33 @@ std::wstring FileTimeToString(LPFILETIME const tFileTime) return std::wstring(sTime); } -BOOL WriteToFile(const std::wstring& sStringToWrite, HANDLE hFile) +std::wstring FileSizeToString(const LARGE_INTEGER iFileSize) +{ + // convert the file size to a string + WCHAR sSize[32]; + _wsetlocale(LC_NUMERIC, L""); + wsprintf(sSize, L"%I64u", iFileSize.QuadPart); + return std::wstring(sSize); +} + +std::wstring FileAttributesToString(const DWORD iAttributes) +{ + // decode attributes + std::wstring sAttributes; + if (iAttributes & FILE_ATTRIBUTE_READONLY) sAttributes += L"R"; + if (iAttributes & FILE_ATTRIBUTE_HIDDEN) sAttributes += L"H"; + if (iAttributes & FILE_ATTRIBUTE_SYSTEM) sAttributes += L"S"; + if (iAttributes & FILE_ATTRIBUTE_DIRECTORY) sAttributes += L"D"; + if (iAttributes & FILE_ATTRIBUTE_ARCHIVE) sAttributes += L"A"; + if (iAttributes & FILE_ATTRIBUTE_TEMPORARY) sAttributes += L"T"; + if (iAttributes & FILE_ATTRIBUTE_COMPRESSED) sAttributes += L"C"; + if (iAttributes & FILE_ATTRIBUTE_OFFLINE) sAttributes += L"O"; + if (iAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) sAttributes += L"N"; + if (iAttributes & FILE_ATTRIBUTE_ENCRYPTED) sAttributes += L"E"; + return sAttributes; +} + +BOOL WriteToFile(const std::wstring& sStringToWrite, HANDLE hFile) noexcept { // see how many characters we need to store as utf-8 int iChars = WideCharToMultiByte(CP_UTF8, 0, diff --git a/Main.cpp b/Main.cpp index 0a54dfd..a047250 100644 --- a/Main.cpp +++ b/Main.cpp @@ -338,7 +338,10 @@ void AnalyzingQueue() // construct the entry ObjectEntry oSubEntry; oSubEntry.IsRoot = false; + oSubEntry.FileSize = { oInfo->EndOfFile.LowPart, oInfo->EndOfFile.HighPart }; oSubEntry.Attributes = oInfo->FileAttributes; + oSubEntry.CreationTime = { oInfo->CreationTime.LowPart, (DWORD) oInfo->CreationTime.HighPart }; + oSubEntry.ModifiedTime = { oInfo->LastWriteTime.LowPart, (DWORD) oInfo->LastWriteTime.HighPart }; oSubEntry.Name += oEntry.Name + ((oEntry.IsRoot && oEntry.Name.back() == '\\') ? L"" : L"\\") + std::wstring(oInfo->FileName, oInfo->FileNameLength / sizeof(WCHAR)); @@ -399,7 +402,14 @@ VOID BeginFileScan() // copy it to a null terminated string oEntryFirst.Name = std::wstring(tPathU.Buffer, tPathU.Length / sizeof(WCHAR)); - oEntryFirst.Attributes = GetFileAttributes(oEntryFirst.Name.c_str()); + + // get common file attributes + WIN32_FILE_ATTRIBUTE_DATA tData; + GetFileAttributesExW(oEntryFirst.Name.c_str(), GetFileExInfoStandard, &tData); + oEntryFirst.FileSize = { tData.nFileSizeLow, (LONG) tData.nFileSizeHigh }; + oEntryFirst.Attributes = tData.dwFileAttributes; + oEntryFirst.CreationTime = tData.ftCreationTime; + oEntryFirst.ModifiedTime = tData.ftLastWriteTime; // free the buffer returned previously RtlFreeUnicodeString(&tPathU); diff --git a/Operation.h b/Operation.h index f5eea19..970a483 100644 --- a/Operation.h +++ b/Operation.h @@ -49,6 +49,9 @@ typedef struct ObjectEntry { std::wstring Name; DWORD Attributes; + FILETIME CreationTime; + FILETIME ModifiedTime; + LARGE_INTEGER FileSize; bool IsRoot; } ObjectEntry; diff --git a/OperationGrantDenyPerms.cpp b/OperationGrantDenyPerms.cpp index 309d34e..983fdfe 100644 --- a/OperationGrantDenyPerms.cpp +++ b/OperationGrantDenyPerms.cpp @@ -35,9 +35,7 @@ OperationGrantDenyPerms::OperationGrantDenyPerms(std::queue& oArgL } // populate default values - std::wstring sCommandUpper(sCommand); - ConvertToUpper(sCommandUpper); - tEa.grfAccessMode = (sCommandUpper == GetCommandAdd()) ? GRANT_ACCESS : DENY_ACCESS; + tEa.grfAccessMode = (_wcsicmp(sCommand.c_str(), GetCommandAdd().c_str()) == 0) ? GRANT_ACCESS : DENY_ACCESS; tEa.grfInheritance = NO_INHERITANCE; tEa.grfAccessPermissions = 0; tEa.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; diff --git a/OperationHelp.cpp b/OperationHelp.cpp index 5d08d14..1b8f50a 100644 --- a/OperationHelp.cpp +++ b/OperationHelp.cpp @@ -132,14 +132,21 @@ Commands That Do Not Alter Security Reports any instance of a null ACL. A null ACL, unlike an empty ACL, allows all access (i.e., similar to an ACE with 'Everyone' with 'Full Control') -/Locate +/Locate This command will write a comma separated value file with the fields of filename, creation time, file modified time, file size and file attributes. The regular expression will perform a case insensitive regular expression search against file name or directory name. To report all data, pass .* as the regular expression. -/Report +/LocateHash [:[:]] + Similar to /Locate, but the report file will also contain the SHA256 hash + of files that match the search criteria. The search criteria can optionally + include a SHA256 hash (in hex) and file size. Specifying file size can + dramatically increase search performance since only files with matching + sizes are read for hash comparison. + +/Report This command will write a comma separated value file with the fields of filename, security descriptor part (e.g., DACL), account name, permissions, and inheritance flags. The regular expression will perform a case diff --git a/OperationLocate.cpp b/OperationLocate.cpp index 4a51b70..21db047 100644 --- a/OperationLocate.cpp +++ b/OperationLocate.cpp @@ -71,37 +71,15 @@ void OperationLocate::ProcessObjectAction(ObjectEntry & tObjectEntry) if (wcsrchr(sFileName, '\\') != nullptr) sFileName = wcsrchr(sFileName, '\\') + 1; if (!std::regex_match(sFileName, tRegex)) return; - // fetch file attribute data - WIN32_FILE_ATTRIBUTE_DATA tData; - if (GetFileAttributesExW(tObjectEntry.Name.c_str(), GetFileExInfoStandard, &tData) == 0) - { - InputOutput::AddError(L"ERROR: Unable to read file attributes."); - } - - // convert the file size to a string - WCHAR sSize[32] = { 0 }; - ULARGE_INTEGER iFileSize; - iFileSize.LowPart = tData.nFileSizeLow; - iFileSize.HighPart = tData.nFileSizeHigh; - setlocale(LC_NUMERIC, ""); - wsprintf(sSize, L"%I64u", iFileSize.QuadPart); - - // decode attributes - std::wstring sAttributes = L""; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) sAttributes += L"R"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) sAttributes += L"H"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) sAttributes += L"S"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) sAttributes += L"D"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) sAttributes += L"A"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) sAttributes += L"T"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) sAttributes += L"C"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) sAttributes += L"O"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) sAttributes += L"N"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) sAttributes += L"E"; + // get common file attributes + const std::wstring sSize = FileSizeToString(tObjectEntry.FileSize); + const std::wstring sAttributes = FileAttributesToString(tObjectEntry.Attributes); + const std::wstring sModifiedTime = FileTimeToString(tObjectEntry.ModifiedTime); + const std::wstring sCreationTime = FileTimeToString(tObjectEntry.CreationTime); // write the string to a file std::wstring sToWrite = std::wstring(L"") + Q(tObjectEntry.Name) + L"," + - Q(FileTimeToString(&tData.ftCreationTime)) + L"," + Q(FileTimeToString(&tData.ftLastWriteTime)) + + Q(sCreationTime) + L"," + Q(sModifiedTime) + L"," + Q(sSize) + L"," + Q(sAttributes) + L"\r\n"; if (WriteToFile(sToWrite, hReportFile) == 0) { diff --git a/OperationLocateHash.cpp b/OperationLocateHash.cpp new file mode 100644 index 0000000..f4730bc --- /dev/null +++ b/OperationLocateHash.cpp @@ -0,0 +1,192 @@ +#include "OperationLocateHash.h" +#include "InputOutput.h" +#include "Functions.h" + +#pragma comment(lib, "bcrypt.lib") +#pragma comment(lib, "crypt32.lib") + +#include +#include + +ClassFactory OperationLocateHash::RegisteredFactory(GetCommand()); + +#define Q(x) L"\"" + (x) + L"\"" + +OperationLocateHash::OperationLocateHash(std::queue & oArgList, const std::wstring & sCommand) : Operation(oArgList) +{ + // exit if there are not enough arguments to parse + std::vector sReportFile = ProcessAndCheckArgs(1, oArgList, L"\\0"); + std::vector sMatchAndArgs = ProcessAndCheckArgs(1, oArgList); + + // fetch params + HANDLE hFile = CreateFile(sReportFile.at(0).c_str(), GENERIC_WRITE, + FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + // see if names could be resolved + if (hFile == INVALID_HANDLE_VALUE) + { + // complain + wprintf(L"ERROR: Could not create file '%s' specified for parameter '%s'.\n", sReportFile.at(0).c_str(), GetCommand().c_str()); + exit(-1); + } + + // register the file handle + hReportFile = RegisterFileHandle(hFile, GetCommand()); + + // if this is the first handle using this file, write out a header + if (hFile == hReportFile) + { + // write out the file type marker + const BYTE hHeader[] = { 0xEF,0xBB,0xBF }; + DWORD iBytes = 0; + if (WriteFile(hFile, &hHeader, _countof(hHeader), &iBytes, nullptr) == 0) + { + wprintf(L"ERROR: Could not write out file type marker '%s'.\n", GetCommand().c_str()); + exit(-1); + } + + // write out the header + std::wstring sToWrite = std::wstring(L"") + Q(L"Path") + L"," + Q(L"Creation Time") + L"," + + Q(L"Modified Time") + L"," + Q(L"Size") + L"," + Q(L"Attributes") + L"," + + Q(L"Hash") + L"\r\n"; + if (WriteToFile(sToWrite, hReportFile) == 0) + { + wprintf(L"ERROR: Could not write header to report file for parameter '%s'.\n", GetCommand().c_str()); + exit(-1); + } + } + + // only flag this to apply to the core object with the file name + AppliesToObject = true; + + // compile the regular expression + try + { + tRegex = std::wregex(sMatchAndArgs.at(0), std::wregex::icase | std::wregex::optimize); + } + catch (const std::regex_error &) + { + wprintf(L"ERROR: Invalid regular expression '%s' specified for parameter '%s'.\n", sMatchAndArgs.at(0).c_str(), GetCommand().c_str()); + exit(-1); + } + + // record specific hash if specified + if (sMatchAndArgs.size() > 1) + { + // convert the sha1 string from hex to binary + aHashToMatch = new BYTE[HASH_IN_BYTES]; + DWORD iBytesRead = HASH_IN_BYTES; + if (CryptStringToBinary(sMatchAndArgs.at(1).c_str(), (DWORD) sMatchAndArgs.at(1).size(), + CRYPT_STRING_HEX_ANY, aHashToMatch, &iBytesRead, NULL, NULL) == FALSE || iBytesRead != HASH_IN_BYTES) + { + wprintf(L"ERROR: Invalid hash '%s' specified for parameter '%s'.\n", sMatchAndArgs.at(1).c_str(), GetCommand().c_str()); + exit(-1); + } + } + + // record specific size if specified + if (sMatchAndArgs.size() > 2) + { + iSizeToMatch = _wtoll(sMatchAndArgs.at(2).c_str()); + } +} + +void OperationLocateHash::ProcessObjectAction(ObjectEntry & tObjectEntry) +{ + // skip directories + if (IsDirectory(tObjectEntry.Attributes)) return; + + // skip any files that do not match the size (if specified) + if (iSizeToMatch != -1 && tObjectEntry.FileSize.QuadPart != iSizeToMatch) return; + + // skip any file names that do not match the regex + const WCHAR* sFileName = tObjectEntry.Name.c_str(); + if (wcsrchr(sFileName, '\\') != nullptr) sFileName = wcsrchr(sFileName, '\\') + 1; + if (!std::regex_match(sFileName, tRegex)) return; + + // initialize hash for this thread + static constexpr size_t iFileBuffer = 2 * 1024 * 1024; + __declspec(thread) static BCRYPT_HASH_HANDLE HashHandle = NULL; + __declspec(thread) static PBYTE Hash = nullptr; + __declspec(thread) static PBYTE FileBuffer = nullptr; + __declspec(thread) static DWORD HashLength = 0; + if (Hash == nullptr) + { + BCRYPT_ALG_HANDLE AlgHandle = NULL; + DWORD ResultLength = 0; + if (BCryptOpenAlgorithmProvider(&AlgHandle, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_HASH_REUSABLE_FLAG) != 0 || + BCryptGetProperty(AlgHandle, BCRYPT_HASH_LENGTH, (PBYTE) &HashLength, sizeof(HashLength), &ResultLength, 0) != 0 || + BCryptCreateHash(AlgHandle, &HashHandle, NULL, 0, NULL, 0, BCRYPT_HASH_REUSABLE_FLAG) != 0 || + (Hash = (PBYTE) malloc(HashLength)) == NULL || + (FileBuffer = (PBYTE) malloc(iFileBuffer)) == NULL) + { + wprintf(L"ERROR: Could not setup hashing environment.\n"); + exit(-1); + } + } + + HANDLE hFile = CreateFile(tObjectEntry.Name.c_str(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + InputOutput::AddError(L"Unable to open file for reading."); + return; + } + + DWORD iReadResult = 0; + DWORD iHashResult = 0; + DWORD iReadBytes = 0; + while ((iReadResult = ReadFile(hFile, FileBuffer, iFileBuffer, &iReadBytes, NULL)) != 0 && iReadBytes > 0) + { + iHashResult = BCryptHashData(HashHandle, FileBuffer, iReadBytes, 0); + if (iHashResult != 0) break; + } + + // done reading data + CloseHandle(hFile); + + // complete hash data + if (BCryptFinishHash(HashHandle, Hash, HashLength, 0) != 0) + { + InputOutput::AddError(L"Could not finalize file data."); + exit(-1); + } + + // file read failed + if (iHashResult != 0 || iReadResult == 0) + { + InputOutput::AddError(L"Could not hash/read file data."); + return; + } + + // skip if a hash was specified and there is no match + if (aHashToMatch != nullptr && memcmp(aHashToMatch, Hash, HASH_IN_BYTES) != 0) + { + return; + } + + // convert to base64 + WCHAR sHash[HASH_IN_HEXCHARS + 1] = L""; + DWORD iHashStringLength = HASH_IN_HEXCHARS + 1; + CryptBinaryToStringW(Hash, HashLength, CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF, + sHash, &iHashStringLength); + + // get common file attributes + const std::wstring sSize = FileSizeToString(tObjectEntry.FileSize); + const std::wstring sAttributes = FileAttributesToString(tObjectEntry.Attributes); + const std::wstring sModifiedTime = FileTimeToString(tObjectEntry.ModifiedTime); + const std::wstring sCreationTime = FileTimeToString(tObjectEntry.CreationTime); + + // check if the target path matches out regex filter + if (true) + { + // write output to file + std::wstring sToWrite = std::wstring(L"") + Q(tObjectEntry.Name) + L"," + + Q(sCreationTime) + L"," + Q(sModifiedTime) + L"," + + Q(sSize) + L"," + Q(sAttributes) + L"," + Q(sHash) + L"," + L"\r\n"; + if (WriteToFile(sToWrite, hReportFile) == 0) + { + InputOutput::AddError(L"Unable to write information to report file."); + } + } +} \ No newline at end of file diff --git a/OperationLocateHash.h b/OperationLocateHash.h new file mode 100644 index 0000000..e1bb518 --- /dev/null +++ b/OperationLocateHash.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "Operation.h" + +class OperationLocateHash : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"LocateHash"; } + static ClassFactory RegisteredFactory; + + // operation specific + static const int HASH_IN_BYTES = (256 / 8); + static const int HASH_IN_HEXCHARS = (HASH_IN_BYTES * 2); + HANDLE hReportFile = INVALID_HANDLE_VALUE; + std::wregex tRegex; + PBYTE aHashToMatch = nullptr; + LONGLONG iSizeToMatch = -1; + +public: + + // overrides + void ProcessObjectAction(ObjectEntry & tObjectEntry) override; + + // constructors + OperationLocateHash(std::queue & oArgList, const std::wstring & sCommand); +}; \ No newline at end of file diff --git a/OperationLocateShortcut.cpp b/OperationLocateShortcut.cpp index 0a8b9a2..cec6bd2 100644 --- a/OperationLocateShortcut.cpp +++ b/OperationLocateShortcut.cpp @@ -73,6 +73,9 @@ OperationLocateShortcut::OperationLocateShortcut(std::queue & oArg void OperationLocateShortcut::ProcessObjectAction(ObjectEntry & tObjectEntry) { + // skip directories + if (IsDirectory(tObjectEntry.Attributes)) return; + // skip any file names that do not match the regex const WCHAR * sFileName = tObjectEntry.Name.c_str(); if (wcsrchr(sFileName, '\\') != nullptr) sFileName = wcsrchr(sFileName, '\\') + 1; @@ -98,26 +101,11 @@ void OperationLocateShortcut::ProcessObjectAction(ObjectEntry & tObjectEntry) InputOutput::AddError(L"ERROR: Unable to read file attributes."); } - // convert the file size to a string - WCHAR sSize[32] = { 0 }; - ULARGE_INTEGER iFileSize; - iFileSize.LowPart = tData.nFileSizeLow; - iFileSize.HighPart = tData.nFileSizeHigh; - setlocale(LC_NUMERIC, ""); - wsprintf(sSize, L"%I64u", iFileSize.QuadPart); - - // decode attributes - std::wstring sAttributes = L""; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) sAttributes += L"R"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) sAttributes += L"H"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) sAttributes += L"S"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) sAttributes += L"D"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) sAttributes += L"A"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) sAttributes += L"T"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) sAttributes += L"C"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) sAttributes += L"O"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) sAttributes += L"N"; - if (tData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) sAttributes += L"E"; + // get common file attributes + const std::wstring sSize = FileSizeToString(tObjectEntry.FileSize); + const std::wstring sAttributes = FileAttributesToString(tObjectEntry.Attributes); + const std::wstring sModifiedTime = FileTimeToString(tObjectEntry.ModifiedTime); + const std::wstring sCreationTime = FileTimeToString(tObjectEntry.CreationTime); // create shortcut interfaces IShellLinkW * oLink = nullptr; @@ -145,8 +133,8 @@ void OperationLocateShortcut::ProcessObjectAction(ObjectEntry & tObjectEntry) { // write output to file std::wstring sToWrite = std::wstring(L"") + Q(tObjectEntry.Name) + L"," + - Q(FileTimeToString(&tData.ftCreationTime)) + L"," + Q(FileTimeToString(&tData.ftLastWriteTime)) + - L"," + Q(sSize) + L"," + Q(sAttributes) + L"," + Q(sTargetPath) + L"," + Q(sWorkingDirectory) + L"\r\n"; + Q(sCreationTime) + L"," + Q(sModifiedTime) + L"," + Q(sSize) + L"," + + Q(sAttributes) + L"," + Q(sTargetPath) + L"," + Q(sWorkingDirectory) + L"\r\n"; if (WriteToFile(sToWrite, hReportFile) == 0) { InputOutput::AddError(L"ERROR: Unable to write security information to report file."); diff --git a/Version.h b/Version.h index e1af996..f2c3e9b 100644 --- a/Version.h +++ b/Version.h @@ -1,4 +1,4 @@ #pragma once -#define VERSION_STRING "1.11.0.1" -#define VERSION_COMMA 1,11,0,1 +#define VERSION_STRING "1.12.0.0" +#define VERSION_COMMA 1,12,0,0 diff --git a/repacls.vcxproj b/repacls.vcxproj index a38a005..cf5e512 100644 --- a/repacls.vcxproj +++ b/repacls.vcxproj @@ -205,6 +205,7 @@ + @@ -234,6 +235,7 @@ + diff --git a/repacls.vcxproj.filters b/repacls.vcxproj.filters index 1f8a1a8..25d528b 100644 --- a/repacls.vcxproj.filters +++ b/repacls.vcxproj.filters @@ -115,6 +115,9 @@ Source\Operations + + Source\Operations + @@ -246,6 +249,9 @@ Includes\Operations + + Includes +