F# programmatically running .fsx script file - scripting

I'm sure this must be something really easy, but I can't seem to make it work. Let's say I have an .fsx script file and want to cause it to be executed programmatically. I'm guessing someone must have blogged about this at some point, but I can't seem to find an example that performs my simple scenario. Basically, I want to programmatically duplicate what happens when you right click on an .fsx file and choose "Run with F# Interactive..."

As asked in a comment, you can set UseShellExecute to false to avoid opening the Windows shell. This way, you can have your output directly in F# shell:
open System.Diagnostics
let execScript script =
let psi = new ProcessStartInfo(#"c:\Program Files\Microsoft F#\v4.0\Fsi.exe")
psi.Arguments <- script
psi.UseShellExecute <- false
let p = Process.Start(psi)
p.WaitForExit()
p.ExitCode

After randomly messing around with the command line args I finally got it to work. I feel a little lame answering my own question, but hopefully it will still help someone else. It turned out my confusion was the proper usage of the command line arguments. If someone has something more elegant or generally useful than what I put I'll award the answer to you.
open System.Diagnostics
let launchExecutable() =
let proc = new Process()
proc.StartInfo.FileName <- #"C:\Program Files (x86)\Microsoft F#\v4.0\fsi.exe"
proc.StartInfo.Arguments <- #"--exec --nologo pathToFSXFile\Test.fsx"
proc.Start()
launchExecutable();;

Related

How To Compress Folder-Contents in 1 Statement on Windows?

I'm attempting to zip a folder containing subfolders and items, using Windows shell CopyHere command:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms723207(v=vs.85).aspx
Update: Note, prefer a native solution-- this is for a distributed Excel VBA tool, so bundling 3rd-party files is not ideal. And, need synchronous compression.
I can easily add a folder and its contents to the zip:
oShell.Namespace(sZipPath).CopyHere "C:\My Folder"
So we know CopyHere can process multiple objects inside a folder in 1 statement.
The problem is, the above command puts the containing-folder at the root of the zip, and it's contents inside of it. But, i don't want the containing folder-- just its contents.
The doc mentions a wildcard (option 128), but when i use a wildcard, i get an error:
oShell.Namespace(sZipPath).CopyHere "C:\My Folder\*"
The file name you specified is not valid or too long.
Perhaps there's a way to use my 1st command above, and then move the items in the zip to the root of the zip?
It would be acceptable to loop through each item in the source folder, adding one at a time to the zip. But, because CopyHere is asynchronous, each subsequent CopyHere fails if the previous CopyHere is not finished. None of the fixes work for this issue:
Comparing number of items in source-folder and destination-zip fail, because if the zip contains a folder, that counts as only 1 item (the items it contains are not counted. https://stackoverflow.com/a/16603850/209942
Waiting a while between each item works, but a timer is unacceptable: it's arbitrary. I cannot guess in advance the size or compress-time of each object.
Checking to see if the zip is locked for access failed for me. If I block my loop until the file is not locked, I still get a file-access error. https://stackoverflow.com/a/6666663/209942
Function FileIsOpen(sPathname As String) As Boolean ' true if file is open
Dim lFileNum As Long
lFileNum = FreeFile
Dim lErr As Long
On Error Resume Next
Open sPathname For Binary Access Read Write Lock Read Write As #lFileNum
lErr = Err
Close #lFileNum
On Error GoTo 0
FileIsOpen = (lErr <> 0)
End Function
Update: VBA can call shell commands synchronously (instead of creating a shell32.shell object in VBA), so if CopyHere works on command-line or PowerShell, that could be the solution. Investigating...
Automating Shell objects really isn't a viable approach as you have already discovered. The Explorer Shell doesn't really expose this capability in any other manner though, at least not before Windows Vista and then not in any fashion easily used from VB6 programs or VBA macros.
Your best bet is a 3rd party ActiveX library, but be careful about 64-bit VBA hosts where you'll need a 64-bit version of such a library.
Another option is to acquire a later copy of the zlibwapi.dll and use some VB6 wrapper code with it. This is also a 32-bit solution.
That's what Zipper & ZipWriter, Zipping from VB programs does. Considering your requirements (which for some reason includes a fear of the Timer control) you could use the synchronous ZipperSync Class. See post #4 there. That code includes a simple AddFolderToZipperSync bundling up the logic to add a folder instead of just a single file.
The downside of the synchronous class is that a large archival operation freezes your program UI until it completes. If you don't want that use the Zipper UserControl instead.
You could also take the ideas from that to write your own wrapper class.
Solution:
Windows contains another native compression utility: CreateFromDirectory at a PowerShell prompt.
https://msdn.microsoft.com/en-us/library/system.io.compression.zipfile.createfromdirectory(v=vs.110).aspx
https://blogs.technet.microsoft.com/heyscriptingguy/2015/03/09/use-powershell-to-create-zip-archive-of-folder/
This requires .Net 4.0 or later:
> Add-Type -AssemblyName System.IO.Compression
> $src = "C:\Users\v1453957\documents\Experiment\rezip\aFolder"
> $zip="C:\Users\v1453957\Documents\Experiment\rezip\my.zip"
> [io.compression.zipfile]::CreateFromDirectory($src, $zip)
Note, you may have to provide the complete pathnames-- active directory was not implicit on my machine.
The above compression is synchronous at the PowerShell prompt, as the OP requests.
Next step is executing synchronously from VBA. The solution there is the .Run method in Windows Script Host Object Model. In VBA, set a reference to that, and do the following, setting the 3rd parameter of .Run command, bWaitOnReturn to True:
Function SynchronousShell(sCmd As String)As Long
Dim oWSH As New IWshRuntimeLibrary.WshShell
ShellSynch = oWSH.Run(sCmd, 3, True)
Set oWSH = Nothing
End Function
Now call SynchronousShell, and pass it the entire compression script.
I believe the only way for this process to work is if CreateFromDirectory is executed in the same session as Add-Type.
So, we must pass the whole thing as 1 string. That is, load all 4 commands into a single sCmd variable, so that Add-Type remains associated with the subsequent CreateFromDirectory. In PowerShell syntax, you can separate them with ;
https://thomas.vanhoutte.be/miniblog/execute-multiple-powershell-commands-on-one-line/
Also, you'll want to use single-quotes instead of double-quotes, else double quotes around the strings are removed when the daisy-chained commands are passed to powershell.exe
https://stackoverflow.com/a/39801732/209942
sCmd = "ps4 Add-Type -AssemblyName System.IO.Compression; $src = 'C:\Users\v1453957\documents\Experiment\rezip\aFolder'; $zip='C:\Users\v1453957\Documents\Experiment\rezip\my.zip'; [io.compression.zipfile]::CreateFromDirectory($src, $zip)"
Solved. The above constitutes the complete solution.
Extra info: Additional comments below are for special circumstances:
Multi-version .Net environments
If a .NET < 4.0 is the active environment on your OS, then System.IO.Compression does not exist-- the Add-Type command will fail. But if your machine has the .NET 4 assemblies available, you can still do this:
Create a batch file which runs PowerShell with .Net 4. See https://stackoverflow.com/a/31279372
In your Add-Type command above, use the exact path to the .Net 4 Compression assembly. On my Win Server 2008:
Add-Type -Path "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.IO.Compression.FileSystem\v4.0_4.0.0.0__b77a5c561934e089\System.IO.Compression.FileSystem.dll"
Portability
Turns out, on my machine, I can copy the compression dll to any folder, and make calls to the copy and it works:
Add-Type -Path "C:\MyFunnyFolder\System.IO.Compression.FileSystem.dll"
I don't know what's required to ensure this works-- it might require the full .Net 4.0 or 2.0 files to be located in their expected directories. I assume the dll makes calls to other .Net assemblies. Maybe we just got lucky with this one :)
Character Limit
Depending on the depth of our paths and filenames, character-count may be a concern. PowerShell may have a 260-character limit (not sure).
https://support.microsoft.com/en-us/kb/830473
https://social.technet.microsoft.com/Forums/windowsserver/en-US/f895d766-5ffb-483f-97bc-19ac446da9f8/powershell-command-size-limit?forum=winserverpowershell
Since .Run goes through the Windows shell, you also have to worry about that character limit, but at 8k+, it's a bit roomier:
https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553
https://stackoverflow.com/a/3205048/209942
Site below offers a 24k+ character method, but i've not studied it yet:
http://itproctology.blogspot.com/2013/06/handling-freakishly-long-strings-from.html
At minimum, since we can put the dll wherever we like, we can put it in a folder near C: root-- keeping our character-count down.
Update: This post shows how we can put the whole thing in a script-file, and call it with ps4.cmd. This may become my preferred answer:
.\ps4.cmd GC .\zipper.ps1 | IEX
-- depending on answer here.
CopyHere:
Re the question: can CopyHere command execute on command-line?
CopyHere can be executed directly at PowerShell prompt (code below). However, even in powershell it's asynchronous-- control returns to PowerShell prompt before the process is finished. Therefore, no solution for the OP. Here's how it's done:
> $shellapp=new-object -com shell.application
> $zippath="test.zip"
> $zipobj=$shellapp.namespace((Get-Location).Path + "\$zippath")
> $srcpath="src"
> $srcobj=$shellapp.namespace((Get-Location).Path + "\$srcpath")
> $zipobj.Copyhere($srcobj.items())

Using intellij idea merge as default merge tool in hg

So, i've found this page here, showing how to use intellij's idea merge and diff from command line, and i'm trying to set it as a default for hg.
However, i still have some problems when merging branches (many files):
If hg merge is called while there's no idea instance running, it starts a new instance, show the diff, wait for my response (click on apply / abort). After that, it proceeds to the next file, and do the same. File by file. Works pretty well, but is also veery slow (since it needs to start a new instance every time). Also, as said, there must be no idea instance running, to do that.
If hg merge is called while an idea instance is running, it shows the first diff window, but at the same time starts to merge all other files... This end up in a lot of pop-ups of "file not found" on idea, pointing to tmp files (of .other and .original).
Does anybody know how to do that in a usable way? hg merge using idea merge as default ?
Here's my .hgrc file: [ui]
merge=idea
[merge-tools]
idea =
idea.gui = True
idea.args = merge $local $base $other $output
idea.priority = 1000
idea.premerge = False
Sorry for the bad english, and thanks to all in advance
Hi here are the settings I used successfully.
[ui]
merge=idea
[merge-tools]
idea.args = merge $local $other $base $output
Also ensure that idea is on the path.
My references are the mercurial MergeToolConfiguration and Merging files using IntelliJ IDEA as a command line tool.
Idea does not wait, but gives return too early. A good way to solve this problem is to get mercurial to prompt you.
By setting this configuration in the .hgrc
idea.check = prompt
Mercurial will ask for confirmation on each file. You must finish the merge in Idea before clicking "Yes" on the confirmation box.

Does wscript.exe require a path to a script to work?

I have written some code which runs a script using wscript.exe.
Set oShell = CreateObject("Shell.Application")
oShell.ShellExecute "wscript.exe", strPath2Script, , "runas", 1
Key to this is the "runas" which allows us to run as an admin. This all works fine, but I also have to have additional code elsewhere to create a script file. This seems like a waste, and I would prefer to just send in the code in the file located at strPath2Script as a parameter to wscript.
It doesn't seem like that is possible, and if I have to generate the script, so be it, but is it possible to just send code as a string into wscript.exe rather than have to provide a path to a text file which has the script code?
Thanks for any insights.
Chris
No, you can't just pass code into wscript.exe or cscript.exe. You'll have to write it to a file and pass the filename to wscript.exe or cscript.exe.

Why doesn't this process start?

I'm trying to start the process Store.Client.UI.exe which is located at: "C:\Program Files\Intel\IntelAppStore\bin\Store.Client.UI.exe", or "C:\Program Files (x86)\Intel\IntelAppStore\bin\Store.Client.UI.exe" for 64bit like me, so I use the code:
If My.Settings.instpathtype = 86 Then
Process.Start("C:\Program Files\Intel\IntelAppStore\bin\Store.Client.UI.exe")
Else
Process.Start("C:\Program Files (x86)\Intel\IntelAppStore\bin\Store.Client.UI.exe")
End If
Where my.settings.instpathtype is whether the computer is 64 or 32 bit. But when I run it, it doesn't run Store.Client.UI.exe for some reason. When I go into Explorer and type "C:\Program Files (x86)\Intel\IntelAppStore\bin\Store.Client.UI.exe" it runs Store.Client.UI.exe. What's wrong?
From the code that you posted, I don't know where/how you're getting the value for instpathtype, or what type it is declared as.
But regardless, you really shouldn't be doing it this way. Hard-coding paths to the file system is a very bad practice if you want your code to "Just Work." What you posted above will not only break depending on the bitness of the OS, but also if the user has renamed or moved their Program Files folder. If my boot drive is E:, your code will fail on my computer as well.
Instead, you should be using the special system folders. That way, you don't even need to check whether you're running on a 32-bit or 64-bit operating system. The .NET Framework provides a really easy way of getting at these values with the Environment.GetFolderPath method, and specifying the type of folder you want to retrieve.
In this case, you want the 32-bit Program Files folder, regardless of the host OS's bitness, so you can use the ProgramFilesX86 value to retrieve the appropriate folder, like so:
Process.Start(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) & "\\Intel\\IntelAppStore\\bin\\Store.Client.UI.exe")
When you are encountering problems like this, debugging comes in. Try to display what My.Settings.instpathtype outputs, by a simple MessageBox or similar. If your testing machine is 32 bit, and if the output is different from 86, change it.
EDIT: So I guess you have a 64 bit machine? Try it the other way around. Swap the statements under If and Else, then put My.Settings.instpathtype's output at the condition.
EDIT: If there are no errors on the condition, then it might be because \ is being read as an escape character. You can fix it by adding another \ before it.
If My.Settings.instpathtype = 86 Then
Process.Start("C:\\Program Files\\Intel\\IntelAppStore\\bin\\Store.Client.UI.exe")
Else
Process.Start("C:\\Program Files (x86)\\Intel\\IntelAppStore\\bin\\Store.Client.UI.exe")
End If
It's possible the process is starting and then exiting immediately with an error. Use the return process from Process.Start and check some of its properties, such as proc.exitcode, proc.starttime, and proc.exittime.
dim proc as process
...
proc = Process.Start("C:\\Program Files\\Intel\\IntelAppStore\\bin\\Store.Client.UI.exe")

AIR - Batch File As CMD.exe Argument

AIR doesn't permit launching .bat files as a native process directly, so apparently i'm suppose to set CMD.exe as my startupInfo executable and pass my .bat file and it's arguments.
i can't get it to work, so i'm hoping it's a syntax problem. here is my code:
var testStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
testStartupInfo.executable = new File("C:\\WINDOWS\\system32\\cmd.exe");
var processArguments:Vector.<String> = new Vector.<String>();
processArguments[0] = "/c";
processArguments[1] = "\"C:\\Documents and Settings\\Administrator\\Desktop\\Test\\Test.bat\"";
processArguments[2] = "-testBatPeram1";
processArguments[3] = "-testBatPeram2";
processArguments[4] = "Peram3";
processArguments[5] = "C:\\Documents and Settings\\Administrator\\Desktop\\SaveText.txt";
testStartupInfo.arguments = processArguments;
var test:NativeProcess = new NativeProcess();
test.start(testStartupInfo);
the batch file and its parameters work fine if i manually write them in the command line prompt, so i don't know why nothing is happening when launched from AIR.
Ok i think that by now (3 months later) you have realized that this doesn't work because your bat file path contains spaces.
Have you find any workaround or solution or something?
I have a good approximation that could be enough for you:
Instead of passing parameters to your bat try writing to it through its stdinput.
I mean, instead of passing parameters when calling your bat, treat that info as a variable read in execution.