Encrypted cookies in Chrome - sqlite

I am currently working on a C# forms application that needs to access a specific cookie on my computer, which I can do perfectly fine. Here's the issue:
Google stores cookies in SQLite, and I've downloaded Sqlite database browser to help me look at these values. What surprises me is that about half of the cookie values shows as empty (including the one I need), even though they are obviously not.
The db file is located at:
C:\Users\%username%\AppData\Local\Google\Chrome\User Data\Default\Cookies
On Chrome I have an addon called "Edit This Cookie" which allows me to directly modify cookies on the website I'm on. This addon can read these cookies, and the web browser can parse values through HTTP when needed for different requests, so they are definitely there - still, the SQLite browser, and my custom code both come to the conclusion that the particular value field is empty.
Why is that?
What is it that somehow prevents the field from being read by certain applications?

I've run into this same problem, and the code below provides a working example for anyone who is interested. All credit to Scherling, as the DPAPI was spot on.
public class ChromeCookieReader
{
public IEnumerable<Tuple<string,string>> ReadCookies(string hostName)
{
if (hostName == null) throw new ArgumentNullException("hostName");
var dbPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + #"\Google\Chrome\User Data\Default\Cookies";
if (!System.IO.File.Exists(dbPath)) throw new System.IO.FileNotFoundException("Cant find cookie store",dbPath); // race condition, but i'll risk it
var connectionString = "Data Source=" + dbPath + ";pooling=false";
using (var conn = new System.Data.SQLite.SQLiteConnection(connectionString))
using (var cmd = conn.CreateCommand())
{
var prm = cmd.CreateParameter();
prm.ParameterName = "hostName";
prm.Value = hostName;
cmd.Parameters.Add(prm);
cmd.CommandText = "SELECT name,encrypted_value FROM cookies WHERE host_key = #hostName";
conn.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var encryptedData = (byte[]) reader[1];
var decodedData = System.Security.Cryptography.ProtectedData.Unprotect(encryptedData, null, System.Security.Cryptography.DataProtectionScope.CurrentUser);
var plainText = Encoding.ASCII.GetString(decodedData); // Looks like ASCII
yield return Tuple.Create(reader.GetString(0), plainText);
}
}
conn.Close();
}
}
}

Alright, so in case anyone is interested, I found a solution to this problem after alot of trial, error and googling.
Google Chrome cookies DB has 2 columns for storing values: "value" and "encrypted_value", the latter being used when the cookie stored was requested to be encrypted - often the case with certain confidential information and long-time session keys.
After figuring this out, I then needed to find a way to access this key, stored as a Blob value. I found several guides on how to do this, but the one that ended up paying of was: http://www.codeproject.com/Questions/56109/Reading-BLOB-in-Sqlite-using-C-NET-CF-PPC
Simply reading the value is not enough, as it is encrypted. - Google Chrome uses triple DES encryption with the current users password as seed on windows machines. In order to decrypt this in C#, one should use Windows Data Protection API (DPAPI), there are a few guides out there on how to make use of it.

Like Jasper's answer, in a PowerShell script (of course, customize the SQL query to your needs, and the path to your cookies location):
$cookieLocation = 'C:\Users\John\AppData\Local\Google\Chrome\User Data\Default\cookies'
$tempFileName = [System.IO.Path]::GetTempFileName()
"select writefile('$tempFileName', encrypted_value) from cookies where host_key = 'localhost' and path = '/api' and name = 'sessionId';" | sqlite3.exe "$cookieLocation"
$cookieAsEncryptedBytes = Get-Content -Encoding Byte "$tempFileName"
Remove-Item "$tempFileName"
Add-Type -AssemblyName System.Security
$cookieAsBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($cookieAsEncryptedBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
$cookie = [System.Text.Encoding]::ASCII.GetString($cookieAsBytes)
$cookie

So I wanted to do this without writing to a tempfile every time but also without implementing a separate class as per jasper's solution. Like jasper, I found it easier and quicker to access the System.Data.SQLite.dll available here. It's not as elegant as a separate class, but it's what worked best for me:
Add-Type -AssemblyName System.Security
Add-Type -Path 'C:\Program Files\System.Data.SQLite\2015\bin\x64\System.Data.SQLite.dll'
Function Get-Last-Cookie {
Param(
[Parameter(Mandatory=$True)] $valueName,
[Parameter(Mandatory=$True)] $hostKey,
[Parameter(Mandatory=$True)] $dbDataSource
)
$conn = New-Object -TypeName System.Data.SQLite.SQLiteConnection
$conn.ConnectionString = "Data Source=$dbDataSource"
$conn.Open()
$command = $conn.CreateCommand()
$query = "SELECT encrypted_value FROM cookies WHERE name='$valueName' `
AND host_key='$hostKey' ORDER BY creation_utc DESC LIMIT 1"
$command.CommandText = $query
$adapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter $command
$dataset = New-Object System.Data.DataSet
[void]$adapter.Fill($dataset)
$command.Dispose();
$conn.Close();
$cookieAsEncryptedBytes = $dataset.Tables[0].Rows[0].ItemArray[0]
$cookieAsBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($cookieAsEncryptedBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
return [System.Text.Encoding]::ASCII.GetString($cookieAsBytes)
}
$localAppDataPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)
$cookieDbPath = 'Google\Chrome\User Data\Default\Cookies'
$dbDataSource = Join-Path -Path $localAppDataPath -ChildPath $cookieDbPath
$plainCookie = Get-Last-Cookie 'acct' '.stackoverflow.com' $dbDataSource
Write-Host $plainCookie
I also found the Add-SqliteAssembly function by halr9000 to be very helpful when it came time to schedule my script in the windows task scheduler and realized that the task scheduler runs the x86 version of PowerShell and thus SQLite rather than the x64 I was using in the console.

The thing is that Google Chrome encrypts the data you need to read, so you have to decrypt it.
First, get a copy of the cookies file. Then read it using SQLite3. After that, get the encrypted bytes. And last, use the code below to decrypt it.
You'll need these Nugets:
using System.IO;
using System.Net;
using System.Data.SQLite;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Newtonsoft.Json.Linq;
The code so far:
File.Copy(Environment.GetEnvironmentVariable("APPDATA") + #"/../Local/Google/Chrome/User Data/Default/Cookies", #"./Cookies",true);
SQLiteConnection Cnn = new SQLiteConnection("Data Source=" + #"./Cookies" + ";pooling=false");
Cnn.Open();
SQLiteCommand cmd = new SQLiteCommand("SELECT host_key, name, value, encrypted_value FROM cookies WHERE name='mvrusername' OR name='mvrcookie' OR name='mikuki4'", Cnn);
SQLiteDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
byte[] encryptedData = (byte[])rdr["encrypted_value"];
string encKey = File.ReadAllText(Environment.GetEnvironmentVariable("APPDATA") + #"/../Local/Google/Chrome/User Data/Local State");
encKey = JObject.Parse(encKey)["os_crypt"]["encrypted_key"].ToString();
var decodedKey = System.Security.Cryptography.ProtectedData.Unprotect(Convert.FromBase64String(encKey).Skip(5).ToArray(), null, System.Security.Cryptography.DataProtectionScope.LocalMachine);
const int MAC_BIT_SIZE = 128;
const int NONCE_BIT_SIZE = 96;
using (var cipherStream = new MemoryStream(encryptedData))
using (var cipherReader = new BinaryReader(cipherStream))
{
var nonSecretPayload = cipherReader.ReadBytes(3);
var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(decodedKey), MAC_BIT_SIZE, nonce);
cipher.Init(false, parameters);
var cipherText = cipherReader.ReadBytes(encryptedData.Length);
var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
try
{
var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
cipher.DoFinal(plainText, len);
}
catch (InvalidCipherTextException)
{
}
string _cookie= Encoding.Default.GetString(plainText);
}
}
// Big thanks to https://stackoverflow.com/a/60611673/6481581 for answering how Chrome 80 and up changed the way cookies are encrypted.

# this powershell scripts exports your cookies to a format curl and wget understand
# Obs ! Each profile has its own cookes file , replace me (ysg ;o) with your win usr name
# aka wget -x --load-cookies cookies.txt http://stackoverflow.com/questions/22532870/encrypted-cookies-in-chrome
$cookieLocation = 'C:\Users\ysg\AppData\Local\Google\Chrome\User Data\Profile 1\Cookies'
$curl_cookies_file="C:\var\ygeo.reports.app.futurice.com.cookies.doc-pub-host.txt"
$tempFileName1 = [System.IO.Path]::GetTempFileName()
$tempFileName2 = [System.IO.Path]::GetTempFileName()
# adjust your filter in the where clause ...
"select writefile('$tempFileName1', encrypted_value) from cookies where host_key = '.futurice.com' ;" | sqlite3.exe "$cookieLocation"
$cookieAsEncryptedBytes = Get-Content -Encoding Byte "$tempFileName1"
Remove-Item "$tempFileName1"
Add-Type -AssemblyName System.Security
$cookieAsBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($cookieAsEncryptedBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
$cookie = [System.Text.Encoding]::ASCII.GetString($cookieAsBytes)
$unquoted_cookie=$cookie -replace '"', ""
# adjust your filter in the where clause ...
"
select
host_key
, CASE WHEN httponly=0 THEN 'FALSE' ELSE 'TRUE' END
, path
, CASE WHEN secure=0 THEN 'FALSE' ELSE 'TRUE' END
, expires_utc
, name
, '$unquoted_cookie'
from cookies where host_key = '.futurice.com' ;" | sqlite3.exe -separator " " "$cookieLocation" > $curl_cookies_file
Get-ChildItem *.txt | ForEach-Object { (Get-Content $_) | Out-File -Encoding ASCII $_ }
# check the meta data table
#"PRAGMA table_info([cookies]);" | sqlite3.exe "$cookieLocation"
# src: https://github.com/daftano/cookies.txt/blob/master/src/popup.js
#content += escapeForPre(cookie.domain);
#content += "\t";
#content += escapeForPre((!cookie.hostOnly).toString().toUpperCase());
#content += "\t";
#content += escapeForPre(cookie.path);
#content += "\t";
#content += escapeForPre(cookie.secure.toString().toUpperCase());
#content += "\t";
#content += escapeForPre(cookie.expirationDate ? Math.round(cookie.expirationDate) : "0");
#content += "\t";
#content += escapeForPre(cookie.name);
#content += "\t";
#content += escapeForPre(cookie.value);
#content += "\n";
#
#0|creation_utc|INTEGER|1||1
#1|host_key|TEXT|1||0
#2|name|TEXT|1||0
#3|value|TEXT|1||0
#4|path|TEXT|1||0
#5|expires_utc|INTEGER|1||0
#6|secure|INTEGER|1||0
#7|httponly|INTEGER|1||0
#8|last_access_utc|INTEGER|1||0
#9|has_expires|INTEGER|1|1|0
#10|persistent|INTEGER|1|1|0
#11|priority|INTEGER|1|1|0
#12|encrypted_value|BLOB|0|''|0
#13|firstpartyonly|INTEGER|1|0|0

Just set "value" to the cookie you want, "encrypted_value" to NULL and "priority" to 0

Related

Executable binary file not working after XOR encryption and decyption

I want to encrypt an exe file (file.exe), write the encrypted version to a text file (fileenc.txt) and decrypt the data in the text file back to another exe file (filedec.exe).
file.exe and filedec.exe are the same and are expected to function the same way.
However, when I try to do this the filedec.exe does not work. Error Popup says: "This app cannot run on your PC".
Please what could be the problem?
However, when I just read the file.exe, write to fileenc.txt without encryption or decryption, and then read fileenc.txt and write data to filedec.exe without encryption or decryption, filedec.exe seems to work fine.
Also, when I try encrypting and decrypting a text file with this code, it works fine too.
But when I encrypt and decrypt an exe on the fly, filedec.exe doesn't work.
Please help me out. Thank you everyone.
Here is my full code:
Main();
function Main() {
var arrKey;
arrKey = "encryptionkey";
//Encrypt file.exe and write the encrypted form to file.txt
Crypt( "C:\\...\\file.exe", "C:\\...\\fileenc.txt", arrKey );
//Decrypt the previously encrypted file.txt and write the decrypted form to filedec.exe
Crypt( "C:\\...\\fileenc.txt", "C:\\...\\filedec.exe", arrKey );
//NOTE: file.exe and filedec.exe are expected to work fine when executed
}
function Crypt(fileIn, fileOut, key) {
var fileInRead;
//Read fileIn
var adTypeBinaryRead = 1;
var BinaryStreamRead;
BinaryStreamRead = new ActiveXObject("ADODB.Stream");
BinaryStreamRead.Type = adTypeBinaryRead;
BinaryStreamRead.Open();
BinaryStreamRead.LoadFromFile(fileIn);
fileInRead = BinaryStreamRead.Read();
//Convert fileIn binary data to string
var objRS = new ActiveXObject("ADODB.Recordset");
var DefinedSize = 1024;
var adSaveCreateOverWrite = 2;
var adFldLong = 0x80;
var adVarChar = 201;
var adTypeText = 2;
objRS.Fields.Append("filedata", adVarChar, DefinedSize, adFldLong);
objRS.Open();
objRS.AddNew();
objRS.Fields("filedata").AppendChunk(fileInRead);
var binString = objRS("filedata").value;
objRS.close();
//Make key as long as string version of fileIn
while (key.length < binString.length) {
key += key;
}
key = key;
//crypt converted string with key
var k, ss, q;
var cryptresult = "";
i = 0;
for (var index = 0; index < binString.length; index++) {
k = key.substr(i, 1);
q = binString.substr(i, 1);
ss = q.charCodeAt(0);
cryptresult = cryptresult + String.fromCharCode(q.charCodeAt(0) ^ k.charCodeAt(0));
i = i +1;
}
// write crypted string to file
var outStreamW = new ActiveXObject("ADODB.Stream");
outStreamW.Type = adTypeText;
// Charset: the default value seems to be `UTF-16` (BOM `0xFFFE` for text files)
outStreamW.Open();
outStreamW.WriteText(cryptresult);
outStreamW.Position = 0;
var outStreamA = new ActiveXObject("ADODB.Stream");
outStreamA.Type = adTypeText;
outStreamA.Charset = "windows-1252"; // important, see `cdoCharset Module Constants`
outStreamA.Open();
outStreamW.CopyTo(outStreamA); // convert encoding
outStreamA.SaveToFile(fileOut, adSaveCreateOverWrite);
outStreamW.Close();
outStreamA.Close();
}
EDIT:
More troubleshooting into my code shows that when I encrypt and decrypt file.exe ON THE FLY, and then write the decrypted data to fileenc.exe, fileenc.exe works well.
But when I encrypt file.exe and write the encrypted data to fileenc.txt and then read the fileenc.txt, decrypt the read encrypted data and write to fileenc.exe (just like in my code), fileenc.exe gets corrupted. My understanding suggests that the manner through which I write the encrypted data to fileenc.txt could be the problem here.
Please I need help, how do I go about with this.

How can I read/decrypt the encrypted_value column in the chrome sqlite database using powershell?

I'm trying to read the contents of a cookie (to use for authentication in a script), but the value is stored as some sort of encrypted value in the chrome sqlite database. Is there any way to decrypt this using powershell?
Right now I can read the value out of the database using a script like this:
[string]$sqlite_library_path = "C:\Path\To\System.Data.SQLite.dll"
[string]$db_data_source = "C:\Users\$env:USERNAME\AppData\Local\Google\Chrome\User Data\Default\Cookies"
[string]$db_query = "SELECT * FROM cookies WHERE name='cookiename' AND host_key='servername'"
[void][System.Reflection.Assembly]::LoadFrom($sqlite_library_path)
$db_dataset = New-Object System.Data.DataSet
$db_data_adapter = New-Object System.Data.SQLite.SQLiteDataAdapter($db_query,"Data Source=$db_data_source")
[void]$db_data_adapter.Fill($db_dataset)
$db_dataset.Tables[0].encrypted_value
The problem is that the encryped value that is returned is unusable. How can I convert this into a usable value?
Adapted to Powershell from this answer: Encrypted cookies in Chrome
# Load System.Security assembly
Add-Type -AssemblyName System.Security
# Decrypt cookie
$ByteArr = [System.Security.Cryptography.ProtectedData]::Unprotect(
$db_dataset.Tables[0].encrypted_value,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser
)
# Convert to string
$DecryptedCookie = [System.Text.Encoding]::ASCII.GetString($ByteArr)
I also advise you to change the way you getting the path to the cookies DB, because it's unreliable. In fact, it doesn't works on my machine, because, I've renamed user, but profile folder still keeps it's old name. Use Environment.GetFolderPath method instead:
# Get Cookies DB path
[string]$db_data_source = Join-Path -Path [Environment]::GetFolderPath('LocalApplicationData') -ChildPath 'Google\Chrome\User Data\Default\Cookies'
For those who don't have/want a SQLite assembly, you can use the sqlite command.
Here's a working 1-line example for Bash on MSYS2 (spread over multiple lines for readability).
Fill in the '' blanks in the SQL query as needed.
sqlite3 "${LOCALAPPDATA}\\Google\\Chrome\\User Data\\Default\\Cookies" \
".mode quote" \
"SELECT encrypted_value FROM cookies WHERE host_key = '' AND name = '' AND path = '/'"|\
powershell -command 'function main() {
$newline = [System.Text.Encoding]::ASCII.GetBytes(([char]10).ToString());
$stdout = [System.Console]::OpenStandardOutput();
try {
for ($n = 0; ($s = [System.Console]::In.ReadLine()) -ne $null; ++$n) {
if ($n) { $stdout.Write($newline, 0, $newline.Length); }
$s = [System.Text.RegularExpressions.Regex]::Match($s,
"^X" + [char]39 + "(.*)" + [char]39 + "$").Groups[1].Value;
$b = [byte[]]::new($s.Length / 2);
For ($i=0; $i -lt $s.Length; $i += 2) {
$b[$i / 2] = [System.Convert]::ToByte($s.Substring($i, 2), 16);
}
Add-Type -AssemblyName System.Security;
$output = [System.Security.Cryptography.ProtectedData]::Unprotect($b, $null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser);
$stdout.Write($output, 0, $output.Length);
}
} finally {
$stdout.Close();
}
}
main'

Batch for downloading most recent file (where filename changes on new version) from http website

i need a batch for downloading files from a http website (http://www.rarlab.com/download.htm).
From this website i only need the most recent version for the 32bit and 64bit english
program which is always listed at the top of this website.
Problem 1: There are more than this two files for download on the website
Problem 2: The name of the file changes with every new version
How can i download these 2 files (the most recent version) without knowing the exact file name
(and without first visiting the web page to find out the file name) ??
Maybe i can use wget, curl or aria2 for that task but i don't know the parameters/options.
Can anyone help me solving this problem ?
(Please only batch solutions - no vbs, java, jscript, powershell etc.)
thank you.
Sorry, i forgot to say that i use windows 7 32bit. And i prefer batch because the script should be able to run on all windows versions without having to download extra programs or resource kits for different windows version (as of powershell which must be downloaded for windows xp etc.) - and because i only understand batch scripting.
Here's a batch + JScript hybrid script. I know you said no vbs, java, jscript, etc, but you're going to have an awfully hard time scraping HTML with pure batch. But this does meet your other criteria -- running on all Windows versions without having to rely on optional software (like powershell or .Net).* And with JScript's XMLHTTP object you don't even need a 3rd party app to fetch web content.
As for not understanding JScript, aside from a few proprietary ActiveX objects it's just like JavaScript. In case you aren't familiar with JavaScript or regular expressions, I added copious amounts of comments to help you out. Hopefully whatever I didn't bother commenting is pretty obvious what it does.
Update
The script now detects the system locale, matches it with a language on the WinRAR download page, and downloads that language release.
Anyway, save this with a .bat extension and run it as you would any other batch script.
#if (#a==#b) #end /*
:: batch script portion
#echo off
setlocal
set "url=http://www.rarlab.com/download.htm"
set /p "savepath=Location to save? [%cd%] "
if "%savepath%"=="" set "savepath=%cd%"
cscript /nologo /e:jscript "%~f0" "%url%" "%savepath%"
goto :EOF
:: JScript portion */
// populate translation from locale identifier hex value to WinRAR language label
// http://msdn.microsoft.com/en-us/library/dd318693.aspx
var abbrev={}, a=function(arr,val){for(var i=0;i<arr.length;i++)abbrev[arr[i]]=val};
a(['1401','3c01','0c01','0801','2001','4001','2801','1c01','3801','2401'],'Arabic');
a(['042b'],'Armenian');
a(['082c','042c'],'Azerbaijani');
a(['0423'],'Belarusian');
a(['0402'],'Bulgarian');
a(['0403'],'Catalan');
a(['7c04'],'Chinese Traditional');
a(['0c04','1404','1004','0004'],'Chinese Simplified');
a(['101a'],'Croatian');
a(['0405'],'Czech');
a(['0406'],'Danish');
a(['0813','0413'],'Dutch');
a(['0425'],'Estonian');
a(['040b'],'Finnish');
a(['080c','0c0c','040c','140c','180c','100c'],'French');
a(['0437'],'Georgian');
a(['0c07','0407','1407','1007','0807'],'German');
a(['0408'],'Greek');
a(['040d'],'Hebrew');
a(['040e'],'Hungarian');
a(['0421'],'Indonesian');
a(['0410','0810'],'Italian');
a(['0411'],'Japanese');
a(['0412'],'Korean');
a(['0427'],'Lithuanian');
a(['042f'],'Macedonian');
a(['0414','0814'],'Norwegian');
a(['0429'],'Persian');
a(['0415'],'Polish');
a(['0816'],'Portuguese');
a(['0416'],'Portuguese Brazilian');
a(['0418'],'Romanian');
a(['0419'],'Russian');
a(['7c1a','1c1a','0c1a'],'Serbian Cyrillic');
a(['181a','081a'],'Serbian Latin');
a(['041b'],'Slovak');
a(['0424'],'Slovenian');
a(['2c0a','400a','340a','240a','140a','1c0a','300a','440a','100a','480a','080a','4c0a','180a','3c0a','280a','500a','0c0a','040a','540a','380a','200a'],'Spanish');
a(['081d','041d'],'Swedish');
a(['041e'],'Thai');
a(['041f'],'Turkish');
a(['0422'],'Ukranian');
a(['0843','0443'],'Uzbek');
a(['0803'],'Valencian');
a(['042a'],'Vietnamese');
function language() {
var os = GetObject('winmgmts:').ExecQuery('select Locale from Win32_OperatingSystem');
var locale = new Enumerator(os).item().Locale;
// default to English if locale is not in abbrev{}
return abbrev[locale.toLowerCase()] || 'English';
}
function fetch(url) {
var xObj = new ActiveXObject("Microsoft.XMLHTTP");
xObj.open("GET",url,true);
xObj.setRequestHeader('User-Agent','XMLHTTP/1.0');
xObj.send('');
while (xObj.readyState != 4) WSH.Sleep(50);
return(xObj);
}
function save(xObj, file) {
var stream = new ActiveXObject("ADODB.Stream");
with (stream) {
type = 1; // binary
open();
write(xObj.responseBody);
saveToFile(file, 2); // overwrite
close();
}
}
// fetch the initial web page
var x = fetch(WSH.Arguments(0));
// make HTML response all one line
var html = x.responseText.split(/\r?\n/).join('');
// create array of hrefs matching *.exe where the link text contains system language
var r = new RegExp('<a\\s*href="[^"]+\\.exe(?=[^\\/]+' + language() + ')', 'g');
var anchors = html.match(r)
// use only the first two
for (var i=0; i<2; i++) {
// use only the stuff after the quotation mark to the end
var dl = '' + /[^"]+$/.exec(anchors[i]);
// if the location is a relative path, prepend the domain
if (dl.substring(0,1) == '/') dl = /.+:\/\/[^\/]+/.exec(WSH.Arguments(0)) + dl;
// target is path\filename
var target=WSH.Arguments(1) + '\\' + /[^\/]+$/.exec(dl)
// echo without a new line
WSH.StdOut.Write('Saving ' + target + '... ');
// fetch file and save it
save(fetch(dl), target);
WSH.Echo('Done.');
}
Update 2
Here's the same script with a few minor tweaks to have it also detect the architecture (32/64-bitness) of Windows, and only download one installer instead of two:
#if (#a==#b) #end /*
:: batch script portion
#echo off
setlocal
set "url=http://www.rarlab.com/download.htm"
set /p "savepath=Location to save? [%cd%] "
if "%savepath%"=="" set "savepath=%cd%"
cscript /nologo /e:jscript "%~f0" "%url%" "%savepath%"
goto :EOF
:: JScript portion */
// populate translation from locale identifier hex value to WinRAR language label
// http://msdn.microsoft.com/en-us/library/dd318693.aspx
var abbrev={}, a=function(arr,val){for(var i=0;i<arr.length;i++)abbrev[arr[i]]=val};
a(['1401','3c01','0c01','0801','2001','4001','2801','1c01','3801','2401'],'Arabic');
a(['042b'],'Armenian');
a(['082c','042c'],'Azerbaijani');
a(['0423'],'Belarusian');
a(['0402'],'Bulgarian');
a(['0403'],'Catalan');
a(['7c04'],'Chinese Traditional');
a(['0c04','1404','1004','0004'],'Chinese Simplified');
a(['101a'],'Croatian');
a(['0405'],'Czech');
a(['0406'],'Danish');
a(['0813','0413'],'Dutch');
a(['0425'],'Estonian');
a(['040b'],'Finnish');
a(['080c','0c0c','040c','140c','180c','100c'],'French');
a(['0437'],'Georgian');
a(['0c07','0407','1407','1007','0807'],'German');
a(['0408'],'Greek');
a(['040d'],'Hebrew');
a(['040e'],'Hungarian');
a(['0421'],'Indonesian');
a(['0410','0810'],'Italian');
a(['0411'],'Japanese');
a(['0412'],'Korean');
a(['0427'],'Lithuanian');
a(['042f'],'Macedonian');
a(['0414','0814'],'Norwegian');
a(['0429'],'Persian');
a(['0415'],'Polish');
a(['0816'],'Portuguese');
a(['0416'],'Portuguese Brazilian');
a(['0418'],'Romanian');
a(['0419'],'Russian');
a(['7c1a','1c1a','0c1a'],'Serbian Cyrillic');
a(['181a','081a'],'Serbian Latin');
a(['041b'],'Slovak');
a(['0424'],'Slovenian');
a(['2c0a','400a','340a','240a','140a','1c0a','300a','440a','100a','480a','080a','4c0a','180a','3c0a','280a','500a','0c0a','040a','540a','380a','200a'],'Spanish');
a(['081d','041d'],'Swedish');
a(['041e'],'Thai');
a(['041f'],'Turkish');
a(['0422'],'Ukranian');
a(['0843','0443'],'Uzbek');
a(['0803'],'Valencian');
a(['042a'],'Vietnamese');
function language() {
var os = GetObject('winmgmts:').ExecQuery('select Locale from Win32_OperatingSystem');
var locale = new Enumerator(os).item().Locale;
// default to English if locale is not in abbrev{}
return abbrev[locale.toLowerCase()] || 'English';
}
function fetch(url) {
var xObj = new ActiveXObject("Microsoft.XMLHTTP");
xObj.open("GET",url,true);
xObj.setRequestHeader('User-Agent','XMLHTTP/1.0');
xObj.send('');
while (xObj.readyState != 4) WSH.Sleep(50);
return(xObj);
}
function save(xObj, file) {
var stream = new ActiveXObject("ADODB.Stream");
with (stream) {
type = 1; // binary
open();
write(xObj.responseBody);
saveToFile(file, 2); // overwrite
close();
}
}
// fetch the initial web page
var x = fetch(WSH.Arguments(0));
// make HTML response all one line
var html = x.responseText.split(/\r?\n/).join('');
// get OS architecture (This method is much faster than the Win32_Processor.AddressWidth method)
var os = GetObject('winmgmts:').ExecQuery('select OSArchitecture from Win32_OperatingSystem');
var arch = /\d+/.exec(new Enumerator(os).item().OSArchitecture) * 1;
// get link matching *.exe where the link text contains system language and architecture
var r = new RegExp('<a\\s*href="[^"]+\\.exe(?=[^\\/]+' + language() + '[^<]+' + arch + '\\Wbit)');
var link = r.exec(html)
// use only the stuff after the quotation mark to the end
var dl = '' + /[^"]+$/.exec(link);
// if the location is a relative path, prepend the domain
if (dl.substring(0,1) == '/') dl = /.+:\/\/[^\/]+/.exec(WSH.Arguments(0)) + dl;
// target is path\filename
var target=WSH.Arguments(1) + '\\' + /[^\/]+$/.exec(dl)
// echo without a new line
WSH.StdOut.Write('Saving ' + target + '... ');
// fetch file and save it
save(fetch(dl), target);
WSH.Echo('Done.');

How to check for the existence of an IIS 7 web site via WiX 3.5?

Note: This question can also be found on the WiX mailing list.
I need to be able to check for the existence of an IIS7 website based on the website's description. If the website does not exist I need to cancel the installation. If the website exists I want to continue the installation. I also need to be able to save the site id of the website so that I may use it during an uninstall.
For debugging purposes I have hard coded the website's description. I do not see any indication that a check for the website is being made within the MSI log file. This is the code I am using:
<iis:WebSite Id="IISWEBSITE" Description="Default Web Site" SiteId="*">
<iis:WebAddress Id="IisWebAddress" Port="1"/>
</iis:WebSite>
<Condition Message="Website [IISWEBSITE] not found.">
<![CDATA[IISWEBSITE]]>
</Condition>
Using ORCA I can see that IIsWebAddress and IIsWebSite tables are added to the MSI. The values are:
IIsWebsite
WEB: IISWEBSITE
Description: Default Web Site
KeyAddress: IisWebAddress
Id: -1
IIsWebAddress
Address: IisWebAddress
Web_: IISWEBSITE
Port: 1
Secure: 0
With the above code, the installation is halted with the error message "Website not found". It appears that IISWEBSITE is never getting set. Though, I know that "Default Web Site" exists. I know that I must be missing something, but what?
How can I perform a simple check for the existence of a website in IIS 7?
I too had same problem.
I wrote a custom action to check the version of IIS from registry.
On the basis of registry value create virtual directory
I wrote a custom action in Javascript to do this. If you are assuming IIS7, then you can use the appcmd.exe tool, and just invoke it from within Javascript to get the list of sites. In theory, it's pretty simple to do. But in practice, there's a bunch of hoops you need to jump through.
Here's what I came up with:
function RunAppCmd(command, deleteOutput) {
var shell = new ActiveXObject("WScript.Shell"),
fso = new ActiveXObject("Scripting.FileSystemObject"),
tmpdir = fso.GetSpecialFolder(SpecialFolders.TemporaryFolder),
tmpFileName = fso.BuildPath(tmpdir, fso.GetTempName()),
windir = fso.GetSpecialFolder(SpecialFolders.WindowsFolder),
appcmd = fso.BuildPath(windir,"system32\\inetsrv\\appcmd.exe") + " " + command,
rc;
deleteOutput = deleteOutput || false;
LogMessage("shell.Run("+appcmd+")");
// use cmd.exe to redirect the output
rc = shell.Run("%comspec% /c " + appcmd + "> " + tmpFileName, WindowStyle.Hidden, true);
LogMessage("shell.Run rc = " + rc);
if (deleteOutput) {
fso.DeleteFile(tmpFileName);
}
return {
rc : rc,
outputfile : (deleteOutput) ? null : tmpFileName
};
}
// GetWebSites_Appcmd()
//
// Gets website info using Appcmd.exe, only on IIS7+ .
//
// The return value is an array of JS objects, one per site.
//
function GetWebSites_Appcmd() {
var r, fso, textStream, sites, oneLine, record,
ParseOneLine = function(oneLine) {
// split the string: capture quoted strings, or a string surrounded
// by parens, or lastly, tokens separated by spaces,
var tokens = oneLine.match(/"[^"]+"|\(.+\)|[^ ]+/g),
// split the 3rd string: it is a set of properties separated by colons
props = tokens[2].slice(1,-1),
t2 = props.match(/\w+:.+?(?=,\w+:|$)/g),
bindingsString = t2[1],
ix1 = bindingsString.indexOf(':'),
t3 = bindingsString.substring(ix1+1).split(','),
L1 = t3.length,
bindings = {}, i, split, obj, p2;
for (i=0; i<L1; i++) {
split = t3[i].split('/');
obj = {};
if (split[0] == "net.tcp") {
p2 = split[1].split(':');
obj.port = p2[0];
}
else if (split[0] == "net.pipe") {
p2 = split[1].split(':');
obj.other = p2[0];
}
else if (split[0] == "http") {
p2 = split[1].split(':');
obj.ip = p2[0];
if (p2[1]) {
obj.port = p2[1];
}
obj.hostname = "";
}
else {
p2 = split[1].split(':');
obj.hostname = p2[0];
if (p2[1]) {
obj.port = p2[1];
}
}
bindings[split[0]] = obj;
}
// return the object describing the website
return {
id : t2[0].split(':')[1],
name : "W3SVC/" + t2[0].split(':')[1],
description : tokens[1].slice(1,-1),
bindings : bindings,
state : t2[2].split(':')[1] // started or not
};
};
LogMessage("GetWebSites_Appcmd() ENTER");
r = RunAppCmd("list sites");
if (r.rc !== 0) {
// 0x80004005 == E_FAIL
throw new Exception("ApplicationException", "exec appcmd.exe returned nonzero rc ("+r.rc+")", 0x80004005);
}
fso = new ActiveXObject("Scripting.FileSystemObject");
textStream = fso.OpenTextFile(r.outputfile, OpenMode.ForReading);
sites = [];
// Read from the file and parse the results.
while (!textStream.AtEndOfStream) {
oneLine = textStream.ReadLine();
record = ParseOneLine(oneLine);
LogMessage(" site: " + record.name);
sites.push(record);
}
textStream.Close();
fso.DeleteFile(r.outputfile);
LogMessage("GetWebSites_Appcmd() EXIT");
return sites;
}

ASP.NET: Execute an external executable doesn't work

I need help with the code below.
I try to convert a AutoCAD file from the format dwg to the format dwf.
Then, the dwf file is downloaded and opened on the client computer using a java applet.
The command used to convert the dwg file on the command-line is:
C:\inetpub\wwwroot\COR-Basic\cadviewer\converter\ax2008.exe -i="C:\inetpub\wwwroot\test\container\DU38_EG00_070116.dwg" -o="C:\inetpub\wwwroot\COR-Basic\cadviewer\files\DU38_EG00_070116.dwf" -f=dwf -model -text
this works when I enter the command text in cmd.exe.
But when I call it from my asp.net application, it only starts the process, but the process never ends...
I've tried adding an additional user, have given this user full permission, and full permissions on wwwroot, but it still doesn't work.
Anybody knows what I'm doing wrong, or how I could do it in another way?
If System.IO.File.Exists(strDWGlocation) Then
Dim psiProcessSettings As Diagnostics.ProcessStartInfo = New Diagnostics.ProcessStartInfo
psiProcessSettings.FileName = strApplicationPath
psiProcessSettings.Arguments = " -i=""" & strDWGlocation & """ -o=""" & strOutputLocation & """ -f=dwf -model -text"
'ST-LAPTOP\converter
psiProcessSettings.UserName = "converter"
psiProcessSettings.Password = secureString
'StefanSteiger.Debug.MsgBox("Input location:" + strDWGlocation)
'StefanSteiger.Debug.MsgBox("Output location:" + strOutputLocation)
Response.Write("<h1>Argument1: " + psiProcessSettings.Arguments + "</h1>")
Response.Write("<h1>Pfad1: " + psiProcessSettings.FileName + "</h1>")
'psiProcessSettings.RedirectStandardInput = True
psiProcessSettings.RedirectStandardError = True
psiProcessSettings.RedirectStandardOutput = True 'Redirect output so we can read it.
psiProcessSettings.UseShellExecute = False 'To redirect, we must not use shell execute.
'psiProcessSettings.CreateNoWindow = True ' don't create a window
Dim pConverterProcess As Diagnostics.Process = New Diagnostics.Process
pConverterProcess = Diagnostics.Process.Start(psiProcessSettings) 'Create the process.
pConverterProcess.Start() 'Execute the process.
'Response.Write("<h1>" + Replace(pConverterProcess.StandardOutput.ReadToEnd(), vbCrLf, "<BR />") + "</h1>") 'Send whatever was returned through the output to the client.
'pConverterProcess.CancelOutputRead()
'pConverterProcess.CancelErrorRead()
'pConverterProcess.StandardInput.Close()
'Wait for the process to end.
'pConverterProcess.WaitForExit()
pConverterProcess.Close()
'Dim iExitCode As Integer = pConverterProcess.ExitCode()
pConverterProcess.Dispose()
Else
MyNamespace.Debug.MsgBox("No such file.")
End If
This is my code that does a similar thing, and it works!
process.StartInfo.FileName = toolFilePath;
process.StartInfo.Arguments = parameters;
process.StartInfo.UseShellExecute = false; // needs to be false in order to redirect output
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true; // redirect all 3, as it should be all 3 or none
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(toolFilePath);
process.StartInfo.Domain = domain;
process.StartInfo.UserName = userName;
process.StartInfo.Password = decryptedPassword;
process.Start();
output = process.StandardOutput.ReadToEnd(); // read the output here...
process.WaitForExit(); // ...then wait for exit, as after exit, it can't read the output
returnCode = process.ExitCode;
process.Close(); // once we have read the exit code, can close the process
Why have you commented out the WaitForExit()?
You could try setting EnableRaisingEvents to true as well.
In my experience, the Process class is quite difficult to work with when reading the standard output, try removing any code that attempts to redirect and read output

Resources