RegWrite not writing to the registry - autohotkey

ValueType := A_Args[1]
KeyName := A_Args[2]
ValueName := A_Args[3]
ValueData := A_Args[4]
Loop, %0%
params .= A_Space %A_Index%
; https://autohotkey.com/docs/Run#RunAs
full_command_line := DllCall("GetCommandLine", "str")
if !(A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)")) {
try {
if A_IsCompiled
Run *RunAs "%A_ScriptFullPath%" "%params%" /restart
else
Run *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%" "%params%"
}
ExitApp
}
RegWrite, % ValueType, % KeyName, % ValueName, % ValueData
Why is RegWrite not writing to the registry when I pass parameters to the script?

A_LastError codes
Code 87 means an invalid parameter. What are you passing to RegWrite?
Here's one function I use for debugging. If isCondition is true it shows a custom error message and stops everything.
fAbort(isCondition, sFuncName, sNote, dVars:="") {
If isCondition {
sAbortMessage := % sFuncName ": " sNote
. "`n`nA_LineNumber: """ A_LineNumber """`nErrorLevel: """ ErrorLevel """`nA_LastError: """ A_LastError """`n"
For sName, sValue in dVars
sAbortMessage .= "`n" sName ": """ sValue """"
MsgBox, 16,, % sAbortMessage
ExitApp
}
}
After a RegWrite it could be used like this:
fAbort(ErrorLevel ; 1, if RegWrite unsuccessful.
, "Script or function name here" ; Could use A_ThisFunc for current function name.
, "Registry write unsuccessful." ; Your custom message here.
, { x: "blabla", y: 13 } ; Additional vars you want to see in the msgbox.
)

Related

Is there such function like IsCallable?

I'd like to have in AutoHotkey function named, for example IsCallable, that can tell me whether an object (or anything that can be stored in AHK variable) is callable.
A callable object includes:
a normal function represented as a string.
a function object by Func("somefuncname").
a BoundFunc Object.
The key point is, if fnobj is callable, then the user can write %fnobj%(...) to actually call something.
Clarify my idea with code below:
test_check_callable()
{
fnstr := "fnhello"
fnobjA := Func("fnhello")
fnobjB := fnobjA.bind("0th")
fnobjC := fnobjB
%fnstr%()
%fnobjA%("A1")
%fnobjB%("B1")
fnobjC.("C1", "C2")
r1 := IsCallable(fnstr) ; true
r2 := IsCallable(fnobjA) ; true
r3 := IsCallable(fnobjB) ; true
r4 := IsCallable(fnobjC) ; true
e1 := IsCallable("NoSuch") ; false
e2 := IsCallable({key1:"value1"}) ; false
}
fnhello(args*)
{
Dbgwin_Output("fnhello() parameters: " args.Length())
for i,arg in args
{
Dbgwin_Output(Format(" param{} = {}", i, arg))
}
}
IsCallable(pobj)
{
; How to implement this? IsFunc? IsObject?
}
I hope r1, r2, r3, r4 should all get true, and e1, e2 get false.
I'm using Autohotkey 1.1.36.2 .
PS: Dbgwin_Output() is implemented here: https://github.com/chjfth/AmHotkey/blob/master/libs/debugwin.ahk
If you used AHKv2, you could make use of HasMethod. I'd recommend the usage of AHKv2, it's already at the RC3 stage.
Something like this should work pretty well to cover all basic use cases:
fnStr := "fnHello"
fnObjA := %fnStr%
fnObjB := fnobjA.bind("0th")
fnObjC := fnObjB
class HelloClass
{
Call() => MsgBox("Hello")
}
fnClass := HelloClass()
class NotCallable
{
}
no1 := "NoSuch"
no2 := {key: "value"}
classNo := NotCallable()
MsgBox(
"`"fnHello`": " IsCallable("fnHello") "`n" ;support pure strings
"fnStr: " IsCallable(fnStr) "`n" ;support string objects
"fnObjA: " IsCallable(fnObjA) "`n" ;support user-defined function objects
"fnObjB: " IsCallable(fnObjB) "`n" ;support bound function objects
"fnObjC: " IsCallable(fnObjC) "`n" ;same as fnObjA
"`"MsgBox`": " IsCallable("MsgBox") "`n" ;support built-in functions as pure strings
"MsgBox: " IsCallable(MsgBox) "`n" ;support built-in functions
"fnClass: " IsCallable(fnClass) "`n`n" ;support user defined classes
"`"NoSuch`": " IsCallable("NoSuch") "`n"
"no1: " IsCallable(no1) "`n"
"no2: " IsCallable(no2) "`n"
"classNo: " IsCallable(classNo) "`n"
)
fnHello(param := "")
{
MsgBox("hi " param)
}
IsCallable(inp)
{
if (HasMethod(inp))
return true
try
{
inp := %inp%
}
catch
{
return false
}
return HasMethod(inp)
}
Result:
"fnHello": 1
fnStr: 1
fnObjA: 1
fnObjB: 1
fnObjC: 1
"MsgBox": 1
MsgBox: 1
fnClass: 1
"NoSuch": 0
no1: 0
no2: 0
classNo: 0

Custom command line parameter - distinguish nonexistence from blank? [duplicate]

I am new to Inno Setup and I have already read the documentation. Now I know that Inno Setup can accept different/custom parameter and could be processed via Pascal script. But the problem is, I don't know how to write in Pascal.
I am hoping I could get help about the coding.
I'd like to pass /NOSTART parameter to my setup file which while tell the setup to disable(uncheck) the check mark on "Launch " and if /NOSTART is not provided, it it will enable(check) the check mark "Launch "
or if possible, that Launch page is not required and do everything via code.
Since you can't imperatively modify flags for section entries and directly accessing the RunList would be quite a dirty workaround, I'm using for this two postinstall entries, while one has no unchecked flag specified and the second one has. So, the first entry represents the checked launch check box and the second one unchecked launch check box. Which one is used is controlled by the Check parameter function, where is checked if a command line tail contains /NOSTART parameter.
Also, I've used a little more straightforward function for determining if a certain parameter is contained in the command line tail. It uses the CompareText function to compare text in a case insensitive way. You can replace it with CompareStr function, if you want to compare the parameter text in a case sensitive way. Here is the script:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
[Run]
Filename: "calc.exe"; Description: "Launch calculator"; \
Flags: postinstall nowait skipifsilent; Check: LaunchChecked
Filename: "calc.exe"; Description: "Launch calculator"; \
Flags: postinstall nowait skipifsilent unchecked; Check: not LaunchChecked
[Code]
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
function LaunchChecked: Boolean;
begin
Result := not CmdLineParamExists('/NOSTART');
end;
and so a little research read and read .. i got my answer.
here's my code (except the "GetCommandLineParam")
[Code]
{
var
StartNow: Boolean;
}
function GetCommandLineParam(inParam: String): String;
var
LoopVar : Integer;
BreakLoop : Boolean;
begin
{ Init the variable to known values }
LoopVar :=0;
Result := '';
BreakLoop := False;
{ Loop through the passed in arry to find the parameter }
while ( (LoopVar < ParamCount) and
(not BreakLoop) ) do
begin
{ Determine if the looked for parameter is the next value }
if ( (ParamStr(LoopVar) = inParam) and
( (LoopVar+1) <= ParamCount )) then
begin
{ Set the return result equal to the next command line parameter }
Result := ParamStr(LoopVar+1);
{ Break the loop }
BreakLoop := True;
end;
{ Increment the loop variable }
LoopVar := LoopVar + 1;
end;
end;
{
function InitializeSetup(): Boolean;
var
NOSTART_Value : String;
begin
NOSTART_Value := GetCommandLineParam('/NOSTART');
if(NOSTART_Value = 'false') then
begin
StartNow := True
end
else
begin
StartNow := False
end;
Result := True;
end;
}
procedure CurStepChanged(CurStep: TSetupStep);
var
Filename: String;
ResultCode: Integer;
NOSTART_Value : String;
begin
if CurStep = ssDone then
begin
NOSTART_Value := GetCommandLineParam('/NOSTART');
if(NOSTART_Value = 'false') then
begin
Filename := ExpandConstant('{app}\{#MyAppExeName}');
Exec(Filename, '', '', SW_SHOW, ewNoWait, Resultcode);
end
end;
end;
a code update. Thanks to #TLama
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Break;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
Filename: String;
ResultCode: Integer;
NOSTART_Value : String;
RunApp : Boolean;
begin
if CurStep = ssDone then
begin
RunApp := CmdLineParamExists('/START');
if(RunApp = True) then
begin
Filename := ExpandConstant('{app}\{#MyAppExeName}');
Exec(Filename, '', '', SW_SHOW, ewNoWait, Resultcode);
end
// NOSTART_Value := GetCommandLineParam('/START');
// if(NOSTART_Value = 'true') then
// begin
// Filename := ExpandConstant('{app}\{#MyAppExeName}');
// Exec(Filename, '', '', SW_SHOW, ewNoWait, Resultcode);
//end
end;
end;
How about the following, easy to read
; Script generated by the Inno Script Studio Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Install Specialty Programs"
#define MyAppVersion "1.0"
#define MyAppPublisher ""
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{5}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={pf}\{#MyAppName}
DisableDirPage=yes
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputDir=P:\_Development\INNO Setup Files\Specialty File Install
OutputBaseFilename=Specialty File Install
Compression=lzma
SolidCompression=yes
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "P:\_Development\INNO Setup Files\Specialty File Install\Files\0.0 - Steps.docx"; DestDir: "c:\support\Specialty Files"; Tasks: V00Step
[Tasks]
Name: "Office2013"; Description: "Running Office 2013"; Flags: checkablealone unchecked
Name: "Office2016"; Description: "Running Office 2016"; Flags: checkablealone unchecked
Name: "V00Step"; Description: "Steps To Follow (Read Me)"; Flags: exclusive
[Run]
Filename: "C:\Program Files (x86)\Microsoft Office\Office15\WINWORD.EXE"; Parameters: """c:\support\Specialty Files\0.0 - Steps.docx"""; Description: "Run if Office 2013 is installed"; Tasks: V00Step AND Office2013
Filename: "C:\Program Files (x86)\Microsoft Office\Office16\WINWORD.EXE"; Parameters: """c:\support\Specialty Files\0.0 - Steps.docx"""; Description: "Run if Office 2016 is installed"; Tasks: V00Step AND Office2016

Copy files from COM device using AHK

I have a function which can effevtively copy a file from my android device,
GetDeviceFolder(deviceName) {
shell := ComObjCreate("Shell.Application")
computer := shell.Namespace("::{20d04fe0-3aea-1069-a2d8-08002b30309d}")
for item in computer.Items
if item.Name = deviceName
return item.GetFolder()
}
save_data_file()
{
GuiControlGet,phonename
GuiControlGet,datapath
GuiControlGet,savepath
phone := GetDeviceFolder(phonename)
phone.ParseName(datapath).InvokeVerb("copy")
}
however, I can't figure out how "paste" it to a local drive. I know it's in the clipboard because I can paste it manually after running this function.
The local disk also needs to be handled by COM.
Example:
GetDeviceFolder(deviceName) {
shell := ComObjCreate("Shell.Application")
computer := shell.Namespace("::{20d04fe0-3aea-1069-a2d8-08002b30309d}")
for item in computer.Items
if item.Name = deviceName
return item.GetFolder()
}
save_data_file(src, dest) {
src := StrSplit(src, "\", , 2)
dest := StrSplit(dest, "\", , 2)
GetDeviceFolder(src[1]).ParseName(src[2]).InvokeVerb("copy")
GetDeviceFolder(dest[1]).ParseName(dest[2]).InvokeVerb("paste")
}
save_data_file("Phone Name\Internal Storage\Download\5a5f641e9893c.jpg", "Disk Name (E:)\incoming")
I did it like using this helper function
InvokeVerb(path, menu, validate=True) {
;by A_Samurai
;v 1.0.1 http://sites.google.com/site/ahkref/custom-functions/invokeverb
objShell := ComObjCreate("Shell.Application")
if InStr(FileExist(path), "D") || InStr(path, "::{") {
;~ MsgBox % path
objFolder := objShell.NameSpace(path)
;~ MsgBox % namespace(path) . "k"
objFolderItem := objFolder.Self
}
else {
SplitPath, path, name, dir
;~ MsgBox % path . "`n" name . "`n" . dir
;~ loop, % path0
;~ MsgBox % path%A_index%
objFolder := objShell.NameSpace(dir)
objFolderItem := objFolder.ParseName(name)
}
if validate {
colVerbs := objFolderItem.Verbs
colVerbs.Count
loop % colVerbs.Count {
verb := colVerbs.Item(A_Index - 1)
retMenu := verb.name
StringReplace, retMenu, retMenu, &
if (retMenu = menu) {
verb.DoIt
Return True
}
}
Return False
} else
objFolderItem.InvokeVerbEx(Menu)
}
then I just did this:
InvokeVerb(savepath, "Paste", "false")

Get the value of an input field in IE using AHK

I'm trying to get the value on an input field in IE using auto hot key.
I'm using AHK version 1.1.19.01 on windows 8.1 using IE 11.0.9600.17498
I have a simple (local) html page:
<!DOCTYPE html>
<html>
<head>
<title>Page1</title>
</head>
<body>
<input type="text" name="area2" id="area2" />
</body>
</html>
I type something in the text box and then run the ahk script (which should just tell me the value I typed in).
This is my ahk script:
wb := IEGet("Page1")
WinActivate Page1
txt := wb.Document.All.area2.Value
MsgBox % txt
IEGet(Name="") ;Retrieve pointer to existing IE window/tab
{
IfEqual, Name,, WinGetTitle, Name, ahk_class IEFrame
{
Name := ( Name="New Tab - Windows Internet Explorer" ) ? "about:Tabs" : RegExReplace( Name, " - (Windows|Microsoft) Internet Explorer" )
}
For wb in ComObjCreate( "Shell.Application" ).Windows
{
If ( wb.LocationName = Name ) && InStr( wb.FullName, "iexplore.exe" )
{
Return wb
}
}
}
The message box is blank.
I have tried various syntax to no avail. What am I doing wrong?
The IEGet function was copied from some web page - its not mine, but it works.
NOTES:
To find the ahk version:
msgbox % "my ahk version: " A_AhkVersion
Here is a simple working example ( win7 v1.1.19.01 IE11 )
FileSelectFile, path
wb := ComObjCreate("InternetExplorer.Application")
wb.visible := true
wb.navigate(path)
while wb.readyState!=4 || wb.document.readyState != "complete" || wb.busy
continue
return
f6::
msgbox % wb.document.all["area2"].value
return
I have sometimes also had problems with IEGet() and IE9+
but here is the function I use to get an active IE Object
WBGet(WinTitle="ahk_class IEFrame", Svr#=1) { ;// based on ComObjQuery docs
static msg := DllCall("RegisterWindowMessage", "str", "WM_HTML_GETOBJECT")
, IID := "{0002DF05-0000-0000-C000-000000000046}" ;// IID_IWebBrowserApp
;// , IID := "{332C4427-26CB-11D0-B483-00C04FD90119}" ;// IID_IHTMLWindow2
SendMessage msg, 0, 0, Internet Explorer_Server%Svr#%, %WinTitle%
if (ErrorLevel != "FAIL") {
lResult:=ErrorLevel, VarSetCapacity(GUID,16,0)
if DllCall("ole32\CLSIDFromString", "wstr","{332C4425-26CB-11D0-B483-00C04FD90119}", "ptr",&GUID) >= 0 {
DllCall("oleacc\ObjectFromLresult", "ptr",lResult, "ptr",&GUID, "ptr",0, "ptr*",pdoc)
return ComObj(9,ComObjQuery(pdoc,IID,IID),1), ObjRelease(pdoc)
}
}
}
It Query's the IWebBrowserApp interface and returns a usable IE Comobject
SetTitleMatchMode, 2
wb := WBGet("Page1")
txt := wb.Document.All["area2"].value
MsgBox % txt
Hope it helps

Classic ASP amazon s3 rest authorisation

I am confused on what I am doing wrong here...
<script language="javascript" runat="server">
function GMTNow(){return new Date().toGMTString()}
</script>
<%
Const AWS_BUCKETNAME = "uk-bucketname"
Const AWS_ACCESSKEY = "GOES HERE"
Const AWS_SECRETKEY = "SECRET"
LocalFile = Server.Mappath("/test.jpg")
Dim sRemoteFilePath
sRemoteFilePath = "/files/test.jpg" 'Remote Path, note that AWS paths (in fact they aren't real paths) are strictly case sensitive
Dim strNow
strNow = GMTNow() ' GMT Date String
Dim StringToSign
StringToSign = Replace("PUT\n\nimage/jpeg\n\nx-amz-date:" & strNow & "\n/"& AWS_BUCKETNAME & sRemoteFilePath, "\n", vbLf)
Dim Signature
Signature = BytesToBase64(HMACSHA1(AWS_SECRETKEY, StringToSign))
Dim Authorization
Authorization = "AWS " & AWS_ACCESSKEY & ":" & Signature
Dim AWSBucketUrl
AWSBucketUrl = "http://s3.amazonaws.com/" & AWS_BUCKETNAME
With Server.CreateObject("Microsoft.XMLHTTP")
.open "PUT", AWSBucketUrl & sRemoteFilePath, False
.setRequestHeader "Authorization", Authorization
.setRequestHeader "Content-Type", "image/jpeg"
.setRequestHeader "Host", AWS_BUCKETNAME & ".s3.amazonaws.com"
.setRequestHeader "x-amz-date", strNow
.send GetBytes(LocalFile) 'Get bytes of local file and send
If .status = 200 Then ' successful
Response.Write "<a href="& AWSBucketUrl & sRemoteFilePath &" target=_blank>Uploaded File</a>"
Else ' an error ocurred, consider xml string of error details
Response.ContentType = "text/xml"
Response.Write .responseText
End If
End With
Function GetBytes(sPath)
dim fs,f
set fs=Server.CreateObject("Scripting.FileSystemObject")
set f=fs.GetFile(sPath)
GetBytes = f.Size
set f=nothing
set fs=nothing
End Function
Function BytesToBase64(varBytes)
With Server.CreateObject("MSXML2.DomDocument").CreateElement("b64")
.dataType = "bin.base64"
.nodeTypedValue = varBytes
BytesToBase64 = .Text
End With
End Function
Function HMACSHA1(varKey, varValue)
With Server.CreateObject("System.Security.Cryptography.HMACSHA1")
.Key = UTF8Bytes(varKey)
HMACSHA1 = .ComputeHash_2(UTF8Bytes(varValue))
End With
End Function
Function UTF8Bytes(varStr)
With Server.CreateObject("System.Text.UTF8Encoding")
UTF8Bytes = .GetBytes_4(varStr)
End With
End Function
%>
Now getting the error.
msxml3.dll error '800c0008'
The download of the specified resource has failed.
/s3.asp, line 39
I'd like to explain how S3 Rest Api works as far as I know.First, you need to learn what should be the string to sign Amazon accepts.
Format :
StringToSign = HTTP-Verb + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Date + "\n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
Generating signed string :
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
Passing authorization header:
Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
Unfortunately you'll play byte to byte since there is no any SDK released for classic asp. So, should understand by reading the entire page http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
For string to sign as you can see above in format, there are three native headers are reserved by the API. Content-Type, Content-MD5 and Date. These headers must be exists in the string to sign even your request hasn't them as empty without header name, just its value. There is an exception, Date header must be empty in string to sign if x-amz-date header is already exists in the request. Then, If request has canonical amazon headers, you should add them as key-value pairs like x-amz-headername:value. But, there is another exception need to be considered for multiple headers. Multiple headers should combine to one header with values comma separated.
Correct
x-amz-headername:value1,value2
Wrong
x-amz-headername:value1\n
x-amz-headername:value2
Most importantly, headers must be ascending order by its group in the string to sign. First, reserved headers with ascending order, then canonical headers with ascending order.
I'd recommend using DomDocument functionality to generate Base64 encoded strings.
Additionally instead of a Windows Scripting Component (.wsc files), you could use .Net's interops such as System.Security.Cryptography to generating keyed hashes more effectively with power of System.Text. All of these interoperabilities are available in today's IIS web servers.
So, as an example I wrote the below script just sends a file to bucket you specified. Consider and test it.
Assumed local file name is myimage.jpg and will be uploaded with same name to root of the bucket.
<script language="javascript" runat="server">
function GMTNow(){return new Date().toGMTString()}
</script>
<%
Const AWS_BUCKETNAME = "uk-bucketname"
Const AWS_ACCESSKEY = "GOES HERE"
Const AWS_SECRETKEY = "SECRET"
LocalFile = Server.Mappath("/test.jpg")
Dim sRemoteFilePath
sRemoteFilePath = "/files/test.jpg" 'Remote Path, note that AWS paths (in fact they aren't real paths) are strictly case sensitive
Dim strNow
strNow = GMTNow() ' GMT Date String
Dim StringToSign
StringToSign = Replace("PUT\n\nimage/jpeg\n\nx-amz-date:" & strNow & "\n/"& AWS_BUCKETNAME & sRemoteFilePath, "\n", vbLf)
Dim Signature
Signature = BytesToBase64(HMACSHA1(AWS_SECRETKEY, StringToSign))
Dim Authorization
Authorization = "AWS " & AWS_ACCESSKEY & ":" & Signature
Dim AWSBucketUrl
AWSBucketUrl = "https://" & AWS_BUCKETNAME & ".s3.amazonaws.com"
With Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
.open "PUT", AWSBucketUrl & sRemoteFilePath, False
.setRequestHeader "Authorization", Authorization
.setRequestHeader "Content-Type", "image/jpeg"
.setRequestHeader "Host", AWS_BUCKETNAME & ".s3.amazonaws.com"
.setRequestHeader "x-amz-date", strNow
.send GetBytes(LocalFile) 'Get bytes of local file and send
If .status = 200 Then ' successful
Response.Write "<a href="& AWSBucketUrl & sRemoteFilePath &" target=_blank>Uploaded File</a>"
Else ' an error ocurred, consider xml string of error details
Response.ContentType = "text/xml"
Response.Write .responseText
End If
End With
Function GetBytes(sPath)
With Server.CreateObject("Adodb.Stream")
.Type = 1 ' adTypeBinary
.Open
.LoadFromFile sPath
.Position = 0
GetBytes = .Read
.Close
End With
End Function
Function BytesToBase64(varBytes)
With Server.CreateObject("MSXML2.DomDocument").CreateElement("b64")
.dataType = "bin.base64"
.nodeTypedValue = varBytes
BytesToBase64 = .Text
End With
End Function
Function HMACSHA1(varKey, varValue)
With Server.CreateObject("System.Security.Cryptography.HMACSHA1")
.Key = UTF8Bytes(varKey)
HMACSHA1 = .ComputeHash_2(UTF8Bytes(varValue))
End With
End Function
Function UTF8Bytes(varStr)
With Server.CreateObject("System.Text.UTF8Encoding")
UTF8Bytes = .GetBytes_4(varStr)
End With
End Function
%>
The Amazon Signature must be url encoded in a slightly different way to what VBSCript encodes. The following function will encode the result correctly:
JScript Version:
function amazonEncode(s)
{
return Server.UrlEncode(s).replace(/\+/g,"%20").replace(/\%2E/g,".").replace(/\%2D/g,"-").replace(/\%7E/g,"~").replace(/\%5F/g,"_");
}
VBScript Version:
function amazonEncode(s)
dim retval
retval = Server.UrlEncode(s)
retval = replace(retval,"+","%20")
retval = replace(retval,"%2E",".")
retval = replace(retval,"%2D","-")
retval = replace(retval,"%7E","~")
retval = replace(retval,"%5F","_")
amazonEncode = retval
end function
As for base64, I used .NET's already built functionality for it. I had to create a DLL to wrap it, so that I could use it from JScript (or VBScript).
Here's how to create that dll:
Download the free C# 2010 Express and install it.
You also need to use two other tools that you won’t have a path to, so you will need to add the path to your PATH environment variable, so at a cmd prompt search for regasm.exe, guidgen.exe and sn.exe (you might find several versions – select the one with the latest date).
• cd\
• dir/s regasm.exe
• dir/s sn.exe
• dir/s guidgen.exe
So as an example, a COM object that has just one method which just returns “Hello”:
Our eventual aim is to use it like this:
<%#Language=JScript%>
<%
var x = Server.CreateObject("blah.whatever");
Response.Write(x.someMethod());
%>
or
<%#Language=VBScript%>
<%
dim x
set x = Server.CreateObject("blah.whatever")
Response.Write x.someMethod()
%>
• Start C# and create a new project
• Select “Empty Project”
• Give it a name – this becomes the namespace by default (the blah in the sample above)
• Next save the project (so you know where to go for the next bit). This will create a folder structure like so:
o blah this contains your solution files that the editor needs (blah.sln etc)
 blah this contains your source code and project files
• bin
o Debug the compiled output ends up here
• Next, using the cmd console, navigate to the root blah folder and create a key pair file:
sn –k key.snk
• Next you need a unique guid (enter guidgen at the cmd prompt)
o Select registry format
o Click “New Guid”
o Click “Copy”
• Back to C# editor – from the menu, select Project – Add Class
• Give it a name – this is the whatever in the sample above
• After the opening brace just after the namespace line type:
[GuidAttribute(“paste your guid here”)]
remove the curly brackets from your pasted guid
• You will need to add another “using” at the top
using System.Runtime.InteropServices;
• Finally you need to create someMethod
The final C# code looks like this (the bits in red may be different in your version):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace blah
{
[GuidAttribute("AEF4F27F-9E97-4189-9AD5-64386A1699A7")]
public class whatever
{
public string someMethod()
{
return "Hello";
}
}
}
• Next, from the menu, select Project – Properties
o On the left, select Application and, for the Output type dropdown, select “Class Library”
o On the left, select Signing and tick the “Sign the assembly” box, then browse to the key.snk file you made earlier
o Save the properties (CTRL-S)
• Next build the dll (Press F6) – This will create a dll in the Debug folder
• Open a cmd window as administrator (right click cmd.exe and select “Run as Administrator”)
• Navigate to the Debug folder and enter the following to register the assembly:
regasm blah.dll /tlb:blah.tlb /codebase blah
That’s it – the above is a genuine COM component and will work in other applications, the example below allows for event handling and only really works in ASP due to the default property mechanism of ASP:
The code for the base64 stuff would be:
// returns a base 64 encoded string that has been encrypted with SHA256
// parameters:
// s string to encrypt
// k key to use during encryption
public string getBase64SHA256(string s, string k)
{
HMACSHA256 sha = new HMACSHA256();
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
sha.Key = encoding.GetBytes(k);
byte[] hashBytes = sha.ComputeHash(encoding.GetBytes(s));
return System.Convert.ToBase64String(hashBytes);
}
// returns a base 64 encoded string that has been encrypted with SHA1
// parameters:
// s string to encrypt
// k key to use during encryption
public string getBase64SHA1(string s, string k)
{
HMACSHA1 sha = new HMACSHA1();
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
sha.Key = encoding.GetBytes(k);
byte[] hashBytes = sha.ComputeHash(encoding.GetBytes(s));
return System.Convert.ToBase64String(hashBytes);
}
You would need the relevant usings:
using System.Security.Cryptography;
The signature in full must have all the query string name-value pairs in alphabetical order before computing the SHA and base64. Here is my version of the signature creator function:
function buildAmazonSignature(host,req,qstring)
{
var str="", i, arr = String(qstring).split("&");
for (i=0; i<arr.length; i++)
arr[i] = arr[i].split("=");
arr.sort(amazonSortFunc);
for (i=0; i<arr.length; i++)
{
if (str != "")
str += "&";
str += arr[i][0] + "=" + arr[i][1];
}
str = "GET\n"+host+"\n"+req+"\n"+str;
var utils = Server.CreateObject("FMAG.Utils");
var b64 = utils.getBase64SHA256(str, "xxxxxxxxxx");
utils = null;
return amazonEncode(b64);
}
function amazonSortFunc(a,b)
{
return (a[0]<b[0])?-1:((a[0]>b[0])?1:0);
}
VBScript doesn't have a very good array sort facility, so you'll have to work that one out yourself - sorry
Also I have the timestamp in this format:
YYYY-MM-DDTHH:MM:SSZ
Also the stuff in the query string included the following:
AWSAccessKeyId
SignatureMethod
SignatureVersion
Version
Expires
Action
Hope that helps
Thank you so much for this question, it has been such a great help to start my WSH/VBScript for my S3 backup service ;-)
I do not have much time, so I will not go through the details of the things I have changed from Chris' code, but please find below my little prototype script which works perfectly ;-)
This is just a WSH/VBScript, so you do not need IIS to run it, you just need to paste the content in a file with the ".vbs" extension, and you can then directly execute it ;-)
Option Explicit
'-- Amazon Web Services > My Account > Access Credentials > Access Keys --'
Dim strAccessKeyID: strAccessKeyID = "..."
Dim strSecretAccessKey: strSecretAccessKey = "..."
'-- Parameters: --'
Dim strLocalFile: strLocalFile = "..."
Dim strRemoteFile: strRemoteFile = "..."
Dim strBucket: strBucket = "..."
'-- Authentication: --'
Dim strNowInGMT: strNowInGMT = NowInGMT()
Dim strStringToSign: strStringToSign = _
"PUT" & vbLf & _
"" & vbLf & _
"text/xml" & vbLf & _
strNowInGMT & vbLf & _
"/" & strBucket + "/" & strRemoteFile
Dim strSignature: strSignature = ConvertBytesToBase64(HMACSHA1(strSecretAccessKey, strStringToSign))
Dim strAuthorization: strAuthorization = "AWS " & strAccessKeyID & ":" & strSignature
'-- Upload: --'
Dim xhttp: Set xhttp = CreateObject("MSXML2.ServerXMLHTTP")
xhttp.open "PUT", "http://" & strBucket & ".s3.amazonaws.com/" & strRemoteFile, False
xhttp.setRequestHeader "Content-Type", "text/xml"
xhttp.setRequestHeader "Date", strNowInGMT 'Yes, this line is mandatory ;-) --'
xhttp.setRequestHeader "Authorization", strAuthorization
xhttp.send GetBytesFromFile(strLocalFile)
If xhttp.status = "200" Then
WScript.Echo "The file has been successfully uploaded ;-)"
Else
WScript.Echo "There was an error :-(" & vbCrLf & vbCrLf & _
xhttp.responseText
End If
Set xhttp = Nothing
'-- NowInGMT ------------------------------------------------------------------'
Function NowInGMT()
'This is probably not the best implementation, but it works ;-) --'
Dim sh: Set sh = WScript.CreateObject("WScript.Shell")
Dim iOffset: iOffset = sh.RegRead("HKLM\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias")
Dim dtNowGMT: dtNowGMT = DateAdd("n", iOffset, Now())
Dim strDay: strDay = "NA"
Select Case Weekday(dtNowGMT)
Case 1 strDay = "Sun"
Case 2 strDay = "Mon"
Case 3 strDay = "Tue"
Case 4 strDay = "Wed"
Case 5 strDay = "Thu"
Case 6 strDay = "Fri"
Case 7 strDay = "Sat"
Case Else strDay = "Error"
End Select
Dim strMonth: strMonth = "NA"
Select Case Month(dtNowGMT)
Case 1 strMonth = "Jan"
Case 2 strMonth = "Feb"
Case 3 strMonth = "Mar"
Case 4 strMonth = "Apr"
Case 5 strMonth = "May"
Case 6 strMonth = "Jun"
Case 7 strMonth = "Jul"
Case 8 strMonth = "Aug"
Case 9 strMonth = "Sep"
Case 10 strMonth = "Oct"
Case 11 strMonth = "Nov"
Case 12 strMonth = "Dec"
Case Else strMonth = "Error"
End Select
Dim strHour: strHour = CStr(Hour(dtNowGMT))
If Len(strHour) = 1 Then strHour = "0" & strHour End If
Dim strMinute: strMinute = CStr(Minute(dtNowGMT))
If Len(strMinute) = 1 Then strMinute = "0" & strMinute End If
Dim strSecond: strSecond = CStr(Second(dtNowGMT))
If Len(strSecond) = 1 Then strSecond = "0" & strSecond End If
Dim strNowInGMT: strNowInGMT = _
strDay & _
", " & _
Day(dtNowGMT) & _
" " & _
strMonth & _
" " & _
Year(dtNowGMT) & _
" " & _
strHour & _
":" & _
strMinute & _
":" & _
strSecond & _
" +0000"
NowInGMT = strNowInGMT
End Function
'-- GetBytesFromString --------------------------------------------------------'
Function GetBytesFromString(strValue)
Dim stm: Set stm = CreateObject("ADODB.Stream")
stm.Open
stm.Type = 2
stm.Charset = "ascii"
stm.WriteText strValue
stm.Position = 0
stm.Type = 1
GetBytesFromString = stm.Read
Set stm = Nothing
End Function
'-- HMACSHA1 ------------------------------------------------------------------'
Function HMACSHA1(strKey, strValue)
Dim sha1: Set sha1 = CreateObject("System.Security.Cryptography.HMACSHA1")
sha1.key = GetBytesFromString(strKey)
HMACSHA1 = sha1.ComputeHash_2(GetBytesFromString(strValue))
Set sha1 = Nothing
End Function
'-- ConvertBytesToBase64 ------------------------------------------------------'
Function ConvertBytesToBase64(byteValue)
Dim dom: Set dom = CreateObject("MSXML2.DomDocument")
Dim elm: Set elm = dom.CreateElement("b64")
elm.dataType = "bin.base64"
elm.nodeTypedValue = byteValue
ConvertBytesToBase64 = elm.Text
Set elm = Nothing
Set dom = Nothing
End Function
'-- GetBytesFromFile ----------------------------------------------------------'
Function GetBytesFromFile(strFileName)
Dim stm: Set stm = CreateObject("ADODB.Stream")
stm.Type = 1 'adTypeBinary --'
stm.Open
stm.LoadFromFile strFileName
stm.Position = 0
GetBytesFromFile = stm.Read
stm.Close
Set stm = Nothing
End Function
Dear stone-edge-technology-VBScript-mates (*), let me know if it is working for you as well ;-)
(*) This is a reference to the comment from Spudley, see above ;-)