mirror of
https://github.com/veracrypt/VeraCrypt
synced 2024-11-24 12:03:28 +01:00
Windows Setup: implement removal of special LEGACY_VERACRYPT registry keys.
This commit is contained in:
parent
ed604cf0f3
commit
ec4be21492
@ -124,6 +124,152 @@ BOOL StatRemoveDirectory (char *lpszDir)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Recursively set the given OWNER security descriptor to the key and its subkeys */
|
||||||
|
static void RecursiveSetOwner (HKEY hKey, PSECURITY_DESCRIPTOR pSD)
|
||||||
|
{
|
||||||
|
LSTATUS status = 0;
|
||||||
|
DWORD dwIndex = 0, dwMaxNameLen = 0, dwNameLen = 0, numberSubKeys = 0;
|
||||||
|
HKEY hSubKey;
|
||||||
|
|
||||||
|
if ( (ERROR_SUCCESS == status) && (ERROR_SUCCESS == RegQueryInfoKey(hKey, NULL, NULL, NULL, &numberSubKeys, &dwMaxNameLen, NULL, NULL, NULL, NULL, NULL, NULL))
|
||||||
|
&& (numberSubKeys >= 1)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
dwMaxNameLen++;
|
||||||
|
char* szNameValue = new char[dwMaxNameLen];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
dwNameLen = dwMaxNameLen;
|
||||||
|
status = RegEnumKeyExA (hKey, dwIndex++, szNameValue, &dwNameLen, NULL, NULL, NULL, NULL);
|
||||||
|
if (status == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
status = RegOpenKeyExA (hKey, szNameValue, 0, WRITE_OWNER | KEY_READ , &hSubKey);
|
||||||
|
if (ERROR_SUCCESS == status)
|
||||||
|
{
|
||||||
|
RecursiveSetOwner (hSubKey, pSD);
|
||||||
|
RegCloseKey(hSubKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delete [] szNameValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegSetKeySecurity (hKey, OWNER_SECURITY_INFORMATION, pSD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recursively set the given DACL security descriptor to the key and its subkeys */
|
||||||
|
static void RecursiveSetDACL (HKEY hKey, const char* SubKeyName, PSECURITY_DESCRIPTOR pSD)
|
||||||
|
{
|
||||||
|
HKEY hSubKey;
|
||||||
|
DWORD dwIndex = 0, dwMaxNameLen = 0, dwNameLen = 0, numberSubKeys = 0;
|
||||||
|
LSTATUS status = RegOpenKeyExA(hKey, SubKeyName, 0, WRITE_DAC | KEY_READ /*| ACCESS_SYSTEM_SECURITY*/, &hSubKey);
|
||||||
|
if (status == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
status = RegSetKeySecurity (hSubKey, DACL_SECURITY_INFORMATION, pSD);
|
||||||
|
if (status == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
RegCloseKey(hSubKey);
|
||||||
|
status = RegOpenKeyExA(hKey, SubKeyName, 0, WRITE_DAC | KEY_READ , &hSubKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (ERROR_SUCCESS == status)
|
||||||
|
&& (ERROR_SUCCESS == RegQueryInfoKeyA(hSubKey, NULL, NULL, NULL, &numberSubKeys, &dwMaxNameLen, NULL, NULL, NULL, NULL, NULL, NULL))
|
||||||
|
&& (numberSubKeys >= 1)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
dwMaxNameLen++;
|
||||||
|
char* szNameValue = new char[dwMaxNameLen];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
dwNameLen = dwMaxNameLen;
|
||||||
|
status = RegEnumKeyExA (hSubKey, dwIndex++, szNameValue, &dwNameLen, NULL, NULL, NULL, NULL);
|
||||||
|
if (status == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
RecursiveSetDACL (hSubKey, szNameValue, pSD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delete [] szNameValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Correct the key permissions to allow its deletion */
|
||||||
|
static void AllowKeyAccess(HKEY Key,const char* SubKeyName)
|
||||||
|
{
|
||||||
|
LSTATUS RegResult;
|
||||||
|
HKEY SvcKey;
|
||||||
|
DWORD dwLength;
|
||||||
|
HANDLE Token = NULL;
|
||||||
|
PTOKEN_USER pTokenUser;
|
||||||
|
std::string sNewSD;
|
||||||
|
|
||||||
|
RegResult = RegOpenKeyExA(Key, SubKeyName, 0, WRITE_OWNER | KEY_READ, &SvcKey);
|
||||||
|
if (RegResult==ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
dwLength=0;
|
||||||
|
pTokenUser = NULL;
|
||||||
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &Token))
|
||||||
|
{
|
||||||
|
if (!GetTokenInformation(Token, TokenUser, pTokenUser, 0, &dwLength))
|
||||||
|
{
|
||||||
|
if (GetLastError() ==ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
{
|
||||||
|
pTokenUser = (PTOKEN_USER) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);
|
||||||
|
if (pTokenUser)
|
||||||
|
{
|
||||||
|
if (GetTokenInformation(Token, TokenUser, pTokenUser, dwLength, &dwLength))
|
||||||
|
{
|
||||||
|
SECURITY_DESCRIPTOR SecDesc;
|
||||||
|
if ( InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION)
|
||||||
|
&& SetSecurityDescriptorDacl(&SecDesc, TRUE, NULL, FALSE) // NULL DACL: full access to everyone
|
||||||
|
&& SetSecurityDescriptorOwner(&SecDesc, pTokenUser->User.Sid, FALSE)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RecursiveSetOwner(SvcKey, &SecDesc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RegCloseKey(SvcKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pTokenUser)
|
||||||
|
{
|
||||||
|
PSID pSid = pTokenUser->User.Sid;
|
||||||
|
DWORD dwAclSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + ::GetLengthSid(pSid) - sizeof(DWORD);
|
||||||
|
PACL pDacl = (PACL) new BYTE[dwAclSize];
|
||||||
|
|
||||||
|
if (TRUE == ::InitializeAcl(pDacl, dwAclSize, ACL_REVISION))
|
||||||
|
{
|
||||||
|
if (TRUE == AddAccessAllowedAceEx(pDacl, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, WRITE_DAC | KEY_ALL_ACCESS, pSid))
|
||||||
|
{
|
||||||
|
SECURITY_DESCRIPTOR SecDesc;
|
||||||
|
if (TRUE == ::InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION))
|
||||||
|
{
|
||||||
|
if (TRUE == ::SetSecurityDescriptorDacl(&SecDesc, TRUE, pDacl, FALSE))
|
||||||
|
{
|
||||||
|
RecursiveSetDACL (Key, SubKeyName, &SecDesc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete [] pDacl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pTokenUser)
|
||||||
|
HeapFree(GetProcessHeap(), 0, pTokenUser);
|
||||||
|
if (Token)
|
||||||
|
CloseHandle(Token);
|
||||||
|
}
|
||||||
|
|
||||||
void SearchAndDeleteRegistrySubString (HKEY hKey, const char *subKey, const char *str, BOOL bEnumSubKeys, const char* enumMatchSubStr)
|
void SearchAndDeleteRegistrySubString (HKEY hKey, const char *subKey, const char *str, BOOL bEnumSubKeys, const char* enumMatchSubStr)
|
||||||
{
|
{
|
||||||
HKEY hSubKey = 0;
|
HKEY hSubKey = 0;
|
||||||
@ -134,30 +280,31 @@ void SearchAndDeleteRegistrySubString (HKEY hKey, const char *subKey, const char
|
|||||||
|
|
||||||
if (bEnumSubKeys)
|
if (bEnumSubKeys)
|
||||||
{
|
{
|
||||||
DWORD dwMaxNameLen = 0;
|
DWORD dwMaxNameLen = 0;
|
||||||
if (ERROR_SUCCESS == RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, &dwMaxNameLen, NULL, NULL, NULL, NULL, NULL, NULL))
|
if (ERROR_SUCCESS == RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, &dwMaxNameLen, NULL, NULL, NULL, NULL, NULL, NULL))
|
||||||
{
|
{
|
||||||
dwMaxNameLen++;
|
dwMaxNameLen++;
|
||||||
char* szNameValue = new char[dwMaxNameLen];
|
char* szNameValue = new char[dwMaxNameLen];
|
||||||
dwIndex = 0;
|
dwIndex = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
dwValueNameLen = dwMaxNameLen;
|
dwValueNameLen = dwMaxNameLen;
|
||||||
status = RegEnumKeyExA (hKey, dwIndex++, szNameValue, &dwValueNameLen, NULL, NULL, NULL, NULL);
|
status = RegEnumKeyExA (hKey, dwIndex++, szNameValue, &dwValueNameLen, NULL, NULL, NULL, NULL);
|
||||||
if (status == ERROR_SUCCESS)
|
if (status == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
if (enumMatchSubStr && !strstr(szNameValue, enumMatchSubStr))
|
if (enumMatchSubStr && !strstr(szNameValue, enumMatchSubStr))
|
||||||
continue;
|
continue;
|
||||||
std::string entryName = szNameValue;
|
std::string entryName = szNameValue;
|
||||||
entryName += "\\";
|
entryName += "\\";
|
||||||
entryName += subKey;
|
entryName += subKey;
|
||||||
|
entryName += "\\";
|
||||||
subKeysList.push_back(entryName);
|
subKeysList.push_back(entryName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delete [] szNameValue;
|
delete [] szNameValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -169,51 +316,58 @@ void SearchAndDeleteRegistrySubString (HKEY hKey, const char *subKey, const char
|
|||||||
// if the string to search for is empty, delete the sub key, otherwise, look for matching value and delete them
|
// if the string to search for is empty, delete the sub key, otherwise, look for matching value and delete them
|
||||||
if (subStringLength == 0)
|
if (subStringLength == 0)
|
||||||
{
|
{
|
||||||
SHDeleteKeyA (hKey, ItSubKey->c_str());
|
if (ERROR_ACCESS_DENIED == SHDeleteKeyA (hKey, ItSubKey->c_str()))
|
||||||
|
{
|
||||||
|
// grant permission to delete
|
||||||
|
AllowKeyAccess (hKey, ItSubKey->c_str());
|
||||||
|
|
||||||
|
// try again
|
||||||
|
SHDeleteKeyA (hKey, ItSubKey->c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (RegOpenKeyExA (hKey, ItSubKey->c_str(), 0, KEY_ALL_ACCESS, &hSubKey) == ERROR_SUCCESS)
|
if (RegOpenKeyExA (hKey, ItSubKey->c_str(), 0, KEY_ALL_ACCESS, &hSubKey) == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
DWORD dwMaxNameLen = 0, dwMaxDataLen = 0;
|
DWORD dwMaxNameLen = 0, dwMaxDataLen = 0;
|
||||||
if (ERROR_SUCCESS == RegQueryInfoKey(hSubKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwMaxNameLen, &dwMaxDataLen, NULL, NULL))
|
if (ERROR_SUCCESS == RegQueryInfoKey(hSubKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwMaxNameLen, &dwMaxDataLen, NULL, NULL))
|
||||||
{
|
{
|
||||||
dwMaxNameLen++;
|
dwMaxNameLen++;
|
||||||
char* szNameValue = new char[dwMaxNameLen];
|
char* szNameValue = new char[dwMaxNameLen];
|
||||||
LPBYTE pbData = new BYTE[dwMaxDataLen];
|
LPBYTE pbData = new BYTE[dwMaxDataLen];
|
||||||
|
|
||||||
std::list<std::string> foundEntries;
|
std::list<std::string> foundEntries;
|
||||||
dwIndex = 0;
|
dwIndex = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
dwValueNameLen = dwMaxNameLen;
|
dwValueNameLen = dwMaxNameLen;
|
||||||
dwDataLen = dwMaxDataLen;
|
dwDataLen = dwMaxDataLen;
|
||||||
status = RegEnumValueA(hSubKey, dwIndex++, szNameValue, &dwValueNameLen, NULL, &dwType, pbData, &dwDataLen);
|
status = RegEnumValueA(hSubKey, dwIndex++, szNameValue, &dwValueNameLen, NULL, &dwType, pbData, &dwDataLen);
|
||||||
if (status == ERROR_SUCCESS)
|
if (status == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
if ( (strlen(szNameValue) >= subStringLength && strstr(szNameValue, str))
|
if ( (strlen(szNameValue) >= subStringLength && strstr(szNameValue, str))
|
||||||
|| (dwType == REG_SZ && strlen((char*) pbData) >= subStringLength && strstr((char*) pbData, str))
|
|| (dwType == REG_SZ && strlen((char*) pbData) >= subStringLength && strstr((char*) pbData, str))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
foundEntries.push_back(szNameValue);
|
foundEntries.push_back(szNameValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while ((status == ERROR_SUCCESS) || (status == ERROR_MORE_DATA)); // we ignore ERROR_MORE_DATA errors since
|
} while ((status == ERROR_SUCCESS) || (status == ERROR_MORE_DATA)); // we ignore ERROR_MORE_DATA errors since
|
||||||
// we are sure to use the correct sizes
|
// we are sure to use the correct sizes
|
||||||
|
|
||||||
// delete the entries
|
// delete the entries
|
||||||
if (!foundEntries.empty())
|
if (!foundEntries.empty())
|
||||||
{
|
{
|
||||||
for (std::list<std::string>::iterator It = foundEntries.begin();
|
for (std::list<std::string>::iterator It = foundEntries.begin();
|
||||||
It != foundEntries.end(); It++)
|
It != foundEntries.end(); It++)
|
||||||
{
|
{
|
||||||
RegDeleteValueA (hSubKey, It->c_str());
|
RegDeleteValueA (hSubKey, It->c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete [] szNameValue;
|
delete [] szNameValue;
|
||||||
delete [] pbData;
|
delete [] pbData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RegCloseKey (hSubKey);
|
RegCloseKey (hSubKey);
|
||||||
@ -222,6 +376,44 @@ void SearchAndDeleteRegistrySubString (HKEY hKey, const char *subKey, const char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set the given privilege of the current process */
|
||||||
|
BOOL SetPrivilege(LPTSTR szPrivilegeName, BOOL bEnable)
|
||||||
|
{
|
||||||
|
TOKEN_PRIVILEGES tp;
|
||||||
|
LUID luid;
|
||||||
|
HANDLE hProcessToken;
|
||||||
|
BOOL bStatus = FALSE;
|
||||||
|
|
||||||
|
if ( OpenProcessToken(GetCurrentProcess(),
|
||||||
|
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
|
||||||
|
&hProcessToken) )
|
||||||
|
{
|
||||||
|
if ( LookupPrivilegeValue(
|
||||||
|
NULL,
|
||||||
|
szPrivilegeName,
|
||||||
|
&luid ) )
|
||||||
|
{
|
||||||
|
|
||||||
|
tp.PrivilegeCount = 1;
|
||||||
|
tp.Privileges[0].Luid = luid;
|
||||||
|
tp.Privileges[0].Attributes = bEnable? SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_REMOVED;
|
||||||
|
|
||||||
|
// Enable the privilege
|
||||||
|
bStatus = AdjustTokenPrivileges(
|
||||||
|
hProcessToken,
|
||||||
|
FALSE,
|
||||||
|
&tp,
|
||||||
|
sizeof(TOKEN_PRIVILEGES),
|
||||||
|
(PTOKEN_PRIVILEGES) NULL,
|
||||||
|
(PDWORD) NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hProcessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bStatus;
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT CreateLink (char *lpszPathObj, char *lpszArguments,
|
HRESULT CreateLink (char *lpszPathObj, char *lpszArguments,
|
||||||
char *lpszPathLink)
|
char *lpszPathLink)
|
||||||
{
|
{
|
||||||
@ -872,6 +1064,9 @@ BOOL DoRegUninstall (HWND hwndDlg, BOOL bRemoveDeprecated)
|
|||||||
|
|
||||||
SHDeleteKey (HKEY_LOCAL_MACHINE, "Software\\Classes\\.hc");
|
SHDeleteKey (HKEY_LOCAL_MACHINE, "Software\\Classes\\.hc");
|
||||||
|
|
||||||
|
// enable the SE_TAKE_OWNERSHIP_NAME privilege for this operation
|
||||||
|
SetPrivilege (SE_TAKE_OWNERSHIP_NAME, TRUE);
|
||||||
|
|
||||||
// clean MuiCache list from VeraCrypt entries
|
// clean MuiCache list from VeraCrypt entries
|
||||||
SearchAndDeleteRegistrySubString (HKEY_CLASSES_ROOT, "Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache", "VeraCrypt", FALSE, NULL);
|
SearchAndDeleteRegistrySubString (HKEY_CLASSES_ROOT, "Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache", "VeraCrypt", FALSE, NULL);
|
||||||
|
|
||||||
@ -879,12 +1074,15 @@ BOOL DoRegUninstall (HWND hwndDlg, BOOL bRemoveDeprecated)
|
|||||||
SearchAndDeleteRegistrySubString (HKEY_USERS, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.hc", NULL, TRUE, NULL);
|
SearchAndDeleteRegistrySubString (HKEY_USERS, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.hc", NULL, TRUE, NULL);
|
||||||
SearchAndDeleteRegistrySubString (HKEY_USERS, "Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Compatibility Assistant\\Persisted", "VeraCrypt", TRUE, NULL);
|
SearchAndDeleteRegistrySubString (HKEY_USERS, "Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Compatibility Assistant\\Persisted", "VeraCrypt", TRUE, NULL);
|
||||||
|
|
||||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM", 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
|
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM", 0, KEY_ALL_ACCESS | WRITE_DAC | WRITE_OWNER, &hKey) == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
|
SearchAndDeleteRegistrySubString (hKey, "Enum\\Root\\LEGACY_VERACRYPT", NULL, TRUE, "ControlSet");
|
||||||
SearchAndDeleteRegistrySubString (hKey, "services\\veracrypt", NULL, TRUE, "ControlSet");
|
SearchAndDeleteRegistrySubString (hKey, "services\\veracrypt", NULL, TRUE, "ControlSet");
|
||||||
RegCloseKey(hKey);
|
RegCloseKey(hKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disable the SE_TAKE_OWNERSHIP_NAME privilege for this operation
|
||||||
|
SetPrivilege (SE_TAKE_OWNERSHIP_NAME, FALSE);
|
||||||
|
|
||||||
SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
||||||
}
|
}
|
||||||
@ -1572,7 +1770,7 @@ void DoUninstall (void *arg)
|
|||||||
DoServiceUninstall (hwndDlg, "VeraCryptService");
|
DoServiceUninstall (hwndDlg, "VeraCryptService");
|
||||||
|
|
||||||
GetTempPath (sizeof (temp), temp);
|
GetTempPath (sizeof (temp), temp);
|
||||||
StringCbPrintfA (UninstallBatch, sizeof (UninstallBatch), "%s\\VeraCrypt-Uninstall.bat", temp);
|
StringCbPrintfA (UninstallBatch, sizeof (UninstallBatch), "%sVeraCrypt-Uninstall.bat", temp);
|
||||||
|
|
||||||
UninstallBatch [sizeof(UninstallBatch)-1] = 0;
|
UninstallBatch [sizeof(UninstallBatch)-1] = 0;
|
||||||
|
|
||||||
@ -1582,7 +1780,7 @@ void DoUninstall (void *arg)
|
|||||||
bOK = FALSE;
|
bOK = FALSE;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fprintf (f, ":loop\n"
|
fprintf (f,":loop\n"
|
||||||
"del \"%s%s\"\n"
|
"del \"%s%s\"\n"
|
||||||
"if exist \"%s%s\" goto loop\n"
|
"if exist \"%s%s\" goto loop\n"
|
||||||
"rmdir \"%s\"\n"
|
"rmdir \"%s\"\n"
|
||||||
|
Loading…
Reference in New Issue
Block a user