Embed VBScript in PowerShell Script - One File - powershell

I recently learned a lot messing with batch + VBScript hybrid scripts and while it was great learning and works, it's time to learn PowerShell more thoroughly. But, my favorite part of Batch/VBScript solution is that I can create a single script.cmd file to distribute.
Is there any sort of solution with PowerShell/VBScript? Ideally I think I'd prefer a .ps1 script with embedded VBScript, but interested in knowing my options.
There seems to be some confusion regarding the goal.
One Single File (This is the most important part)
Extension either .ps1 or .vbs
Both POWERSHELL and VBScript inside single file
Bonus:
No writing to external file
Prefacing each line
Having to escape special characters in code
Encoding entire sections of script (overhead CPU intensive operations)
Here is a thoeretical example:
script.{ps1/vbs}:
<!-- : Begin PS1 script
$strString = "Hello PowerShell"
write-host $strString
cscript //nologo "%~f0?.wsf" //job:HELLOWORLD
exit /b
PAUSE
----- Begin wsf script --->
<package>
<job id="HELLOWORLD">
<script language="VBScript">
MsgBox "Hello World VBS"
</script>
</job>
<job id="VBS">
<script language="VBScript">
'Second Script!
</script>
</job>
</package>
Something like this -->
https://stackoverflow.com/a/9074483/5079799
<!-- : Begin batch script
#ECHO OFF
CLS
cscript //nologo "%~f0?.wsf" //job:HELLOWORLD
exit /b
PAUSE
----- Begin wsf script --->
<package>
<job id="HELLOWORLD">
<script language="VBScript">
MsgBox "Hello World"
</script>
</job>
<job id="VBS">
<script language="VBScript">
'Second Script!
</script>
</job>
</package>

Create the VBS script as usual. Save in some location and then convert it into Base64. Byte encoding is used so that this will work on binary files too, and overcomes character encoding issues. Like so,
$Content = Get-Content -Path C:\temp\myScript.vbs -Encoding Byte
$Base64 = [System.Convert]::ToBase64String($Content)
$Base64 | Out-File c:\temp\myScript.b64
Then, in your Powershell script, include the encoded version of the VBS script. Convert Base64 back into string and write it into a file. Finally, call cscript to run the .vbs.
$Base64 = "ZgB1AG4AYwB0AGkAbwBuACAAR..."
$Content = [System.Convert]::FromBase64String($Base64)
Set-Content -Path $env:temp\myScript.vbs -Value $Content -Encoding Byte
& cscript /nologo $env:temp\myScript.vbs
Another an option is to embed the VBScript in a here-string like so,
# Paste the VBS in a here string
$Content = #'
dim foo
...
'#
Set-Content -Path $env:temp\myScript.vbs -Value $Content
& cscript /nologo $env:temp\myScript.vbs

Perhaps, you mean create a .ps1 script file and run it from vbscript ?
If so, here is an example named as Compress_Archive_by_Extension.vbs
Remark : Compress-Archive is only available with PS v4
Option Explicit
Dim Title,ArrExt,Ext
Title = "Compress Archive With Powreshell And Vbscript by Hackoo 2020"
REM We define an array of extensions for archiving !
ArrExt = Array("vbs","vbe","cmd","bat","ps1","js","jse","lnk")
REM Looping thru extensions defined from our array in order to zip and archive them,
REM so you can add or remove what you want as extension in the array above !
For each Ext in ArrExt
Call Compress_Archive("%Temp%\*."& Ext,"Temp_Archive_"& Ext)
Call Compress_Archive("%AppData%\*."& Ext,"AppData_Archive_"& Ext)
Call Compress_Archive("%LocalAppData%\*."& Ext,"LocalAppData_Archive_"& Ext)
Call Compress_Archive("%ProgramData%\Microsoft\Windows\Start Menu\Programs\Startup\*."& Ext,"ProgramData_Archive_"& Ext)
Call Compress_Archive("%UserProfile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\*."& Ext,"UserProfile_Archive_"& Ext)
Next
MsgBox "Archive Script is completed !",vbInformation,Title
'---------------------------------------------------------------------
REM https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/compress-archive?view=powershell-5.1&redirectedfrom=MSDN
Sub Compress_Archive(Source,Destination)
Const ForWriting = 2
Dim fs,Ws,ts,Ret,PSFile,ByPassPSFile
Set fs = CreateObject("Scripting.FileSystemObject")
Set Ws = WScript.CreateObject("WScript.Shell")
Source = Ws.ExpandEnvironmentStrings(Source)
Destination = Ws.ExpandEnvironmentStrings(Destination)
PSFile = Ws.ExpandEnvironmentStrings("%Temp%") & fs.GetTempName & ".ps1"
ByPassPSFile = "PowerShell -ExecutionPolicy bypass -noprofile -file "
Set ts = fs.OpenTextFile(PSFile,ForWriting,True)
ts.WriteLine "Compress-Archive -Path " & DblQuote(Source) &_
" -Update -CompressionLevel Optimal -DestinationPath "& DblQuote(Destination)
ts.Close
Ret = Ws.run(ByPassPSFile & PSFile,0,True)
If fs.FileExists(PSFile) Then fs.DeleteFile(PSFile)
End Sub
'---------------------------------------------------------------------
Function DblQuote(Str)
DblQuote = Chr(34) & Str & Chr(34)
End Function
'---------------------------------------------------------------------
Second example : To download an image from site : Download_File.vbs
Option Explicit
Dim URL,Ws,ByPassPSFile,PSFile,MyCmd,Result
URL = "https://cdn2.unrealengine.com/Fortnite%2FBoogieDown_GIF-1f2be97208316867da7d3cf5217c2486da3c2fe6.gif"
Set Ws = CreateObject("wscript.Shell")
PSFile = Left(Wscript.ScriptFullName, InstrRev(Wscript.ScriptFullName, ".")) & "ps1"
ByPassPSFile = "cmd /C PowerShell.exe -ExecutionPolicy bypass -noprofile -file "
MyCmd = "$source = " & DblQuote(URL) & VbCrlF
MyCmd = MyCmd & "$Filename = [System.IO.Path]::GetFileName($source)" & VbCrlF
MyCmd = MyCmd & "$dest = " & DblQuote("$env:temp\$Filename") & VbCrlF
MyCmd = MyCmd & "$wc = New-Object System.Net.WebClient" & VbCrlF
MyCmd = MyCmd & "$wc.DownloadFile($source,$dest)" & VbCrlF
MyCmd = MyCmd & "Start-Process $dest"
Call WriteMyPSFile(MyCmd)
Result = Ws.run(ByPassPSFile & PSFile,0,True)
'----------------------------------------------------------------------------------------
Sub WriteMyPSFile(strText)
Dim fs,ts,PSFile
Const ForWriting = 2
PSFile = Left(Wscript.ScriptFullName, InstrRev(Wscript.ScriptFullName, ".")) & "ps1"
Set fs = CreateObject("Scripting.FileSystemObject")
Set ts = fs.OpenTextFile(PSFile,ForWriting,True)
ts.WriteLine strText
ts.Close
End Sub
'----------------------------------------------------------------------------------------
Function DblQuote(Str)
DblQuote = Chr(34) & Str & Chr(34)
End Function
'----------------------------------------------------------------------------------------
EDIT : 21/08/2020 # 20:45
Here is a "pseudo-hybrid" because it use a temporary file to be executed :
inspired from #vonPryz answer.
You can save it as Test.ps1 and execute from PowerShell ISE
$VBS_Content = #'
Dim http, WAN_IP
Set http = CreateObject( "MSXML2.ServerXmlHttp" )
http.Open "GET", "http://icanhazip.com", False
http.Send
WAN_IP = http.responseText
wscript.echo "WAN_IP : " & WAN_IP
'#
Set-Content -Path $env:temp\myScript.vbs -Value $VBS_Content
& wscript.exe $env:temp\myScript.vbs
$url = "https://externals.lesechos.fr/medias/2019/04/26/2262811_pourquoi-salto-le-futur-netflix-francais-devra-seuropeaniser-195514-1.jpg"
#https://stackoverflow.com/questions/35813186/extract-the-filename-from-a-path
$output = $env:temp + "\" + $url.Split("/")[-1]
$start_time = Get-Date
Try {$wb = (New-Object System.Net.WebClient).DownloadFile($url,$output)}
Catch {
Write-Host "Error from $url ! " -ForegroundColor Red -BackgroundColor Yellow
Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor Yellow
}
Write-Output "Running Script Time taken is : $((Get-Date).Subtract($start_time).Seconds) second(s)"
Start-process $output
Another simple example :
$VBS_Content = #'
MsgBox "This a simple MsgBox from Vbscript"
'#
$TmpVBS="$env:temp\myScript.vbs"
SC $TmpVBS $VBS_Content
wscript.exe $TmpVBS
Echo 'Hello World from Powershell !'

Here is my final answer, I haven't tested with anything super complicated, so not sure how it would handle things like special characters...
#https://stackoverflow.com/questions/63514534/embed-vbscript-in-powershell-script-one-file
#######################Begin VBS1#######################
###JOB_A START###
$VBS_Content_Job_A = #'
MsgBox "This a simple MsgBox from Vbscript (Job_A)"
'#
###JOB_A END###
###JOB_B START###
$VBS_Content_Job_B = #'
MsgBox "This a simple MsgBox from Vbscript (Job_B)"
'#
###JOB_B END###
#######################Begin PS1#######################
ECHO 'Hello World from Powershell !'
PAUSE
ECHO "Running VBS Now"
PAUSE
###VBS CALL START###
$VBSJob=$VBS_Content_Job_A
$TmpVBS="$env:temp\myScript.vbs"
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
SC $TmpVBS $VBSJob
cscript //nologo $TmpVBS
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
###VBS CALL END###
ECHO "Some More PowerShell"
PAUSE
ECHO "I need anoter VBS Script"
PAUSE
###VBS CALL START###
$VBSJob=$VBS_Content_Job_B
$TmpVBS="$env:temp\myScript.vbs"
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
Set-Content -Path $TmpVBS -Value $VBSJob
cscript //nologo $TmpVBS
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
###VBS CALL END###
ECHO "All Done!"
PAUSE

You can embed VB.NET code into powershell code with TypeDefinition:
$code = #"
Imports System
Namespace MyNameSpace
Public Class Responder
Public Shared Sub StaticRespond()
Console.WriteLine("Static Response")
End Sub
Public Sub Respond()
Console.WriteLine("Instance Respond")
End Sub
End Class
End Namespace
"#
# Check the type has not been previously added within the session, otherwise an exception is raised
if (-not ([System.Management.Automation.PSTypeName]'MyNameSpace.Responder').Type)
{
Add-Type -TypeDefinition $code -Language VisualBasic;
}
[MyNameSpace.Responder]::StaticRespond();
$instance = New-Object MyNameSpace.Responder;
$instance.Respond();
Not exactly vbscript but is a good solution.

Related

Batch/VBScript/Powershell hybrid script for creating shortcut to shell folders

As stated in Q-title, I am trying to create and automate script for creating shortcuts to Shell-Special folders which doesn't have a valid path per path-string validation rules.
I have tried both Batch/VBScript hybrid and Powershell script for creating shortcut to Target path explorer shell:AppsFolder which when clicked will open that path exactly and not the basic ThisPC or FileExplorer path.
First the Batch/VBScript hybrid that failed:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set /p "Esc_LinkName=Name: "
set /p "Esc_LinkTarget=Target: "
SET cSctVBS=CreateFolderShortcut.vbs
SET LOG=".\%~N0_runtime.log"
((
echo Set oWS = WScript.CreateObject^("WScript.Shell"^)
echo sLinkFile = oWS.ExpandEnvironmentStrings^("%USERPROFILE%\Desktop\!Esc_LinkName!"^)
echo Set oLink = oWS.CreateShortcut^(sLinkFile^)
:: echo oLink.TargetPath = oWS.ExpandEnvironmentStrings^("!Esc_LinkTarget!"^)
echo oLink.TargetPath = "!Esc_LinkTarget!"
echo oLink.Save
)1>!cSctVBS!
cscript //nologo .\!cSctVBS!
DEL !cSctVBS! /f /q
)1>>!LOG! 2>>&1
SETLOCAL DISABLEDELAYEDEXPANSION && ENDLOCAL
pause && goto :eof
which throws error: CreateFolderShortcut.vbs(4, 1) Microsoft VBScript runtime error: Invalid procedure call or argument on this line echo oLink.TargetPath = "!Esc_LinkTarget!" because it can't take target-path explorer shell:AppsFolder.
And now the Powershell script that failed:
$shell = New-Object -ComObject WScript.Shell
$destination = "$env:USERPROFILE\Desktop"
$shortcutPath = Join-Path -Path $destination -ChildPath 'My Apps Folder.lnk'
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = $shell.SpecialFolders.Item("AppsFolder")
$shortcut.Save()
which creates a shortcut to FileExplorer path instead.
Anyone can suggest, how to make this work to get what I want ?
It's definitely worth sticking with a PowerShell script (.ps1), as it doesn't require any trickery (though note that .ps1 files aren't directly executable from outside a PowerShell session):
Replace:
$shortcut.TargetPath = $shell.SpecialFolders.Item("AppsFolder")
with:
$shortcut.TargetPath = 'shell:AppsFolder'
Complete code:
$shell = New-Object -ComObject WScript.Shell
$destination = "$env:USERPROFILE\Desktop"
$shortcutPath = Join-Path -Path $destination -ChildPath 'My Apps Folder.lnk'
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = 'shell:AppsFolder'
$shortcut.Save()

Automating batch files and powershell script execution

The goal is to automate the execution of 3 files:
A batch file that runs a selenium webdriver automated test suite.
Another batch file that generates a reports for the tests run in step 1.
A PowerShell script that attaches the reports and sends an email.
How can I automate the execution of these 3 files so that upon running a command or executing a file, all 3 are executed?
This is how my PowerShell looks like:
$Username = "";
$Password = "";
function Send-ToEmail([string]$email, [string]$attachmentpath) {
$message = New-Object Net.Mail.MailMessage;
$message.From = "c#c.com";
$message.To.Add($email);
$message.Subject = "abc";
$message.Body = "abc";
$attachment1 = New-Object Net.Mail.Attachment($attachmentpath);
$message.Attachments.Add($attachment1);
$smtp = New-Object Net.Mail.SmtpClient("build3", "25");
$smtp.EnableSSL = $false;
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.Send($message);
Write-Host $smtp.EnableSSL;
Write-Host "Mail Sent";
}
Send-ToEmail -email "a#a.com" -attachmentpath "C:\file1";
Send-ToEmail -email "b#b.com" -attachmentpath "C:\file1";
This is what the first batch file looks like:
FOR /F "TOKENS=2 eol=/ DELIMS=/ " %%A IN ('DATE/T') DO SET dd=%%A
FOR /F "TOKENS=2,3 eol=/ DELIMS=/ " %%A IN ('DATE/T') DO SET mm=%%B
FOR /F "TOKENS=2,3,4 eol=/ DELIMS=/ " %%A IN ('DATE/T') DO SET yyyy=%%C
SET todaysdate=%yyyy%%mm%%dd%
start /d "nunitPATH" nunit3-console.exe "seleniumtestsuite dll PATH" --where:cat==SignUp --result="xmlreportPATH"
This is what the second batch file looks like:
start /d "exeFilePATH" ReportUnit.exe "folderPATH"
Simply write a batch script that runs
call script1.bat
call script2.bat
D:\path\to\powershell.exe -File script3.ps1
If you need async:
start script1.bat
start script2.bat
start "" "D:\path\to\powershell.exe" "-File" "script3.ps1"
Note the pair of double quotes before powershell.exe path.
Note that this assumes your device has at least powershell v2
However, this question is a duplicate of here

Powershell write to vbscript console(cscript.exe)

I have a requirement of running powershell script in silent mode through vbscript(cscript.exe).
Basic steps for script are as follow.
vbscript
WScript.StdOut.WriteLine "Welcome..."
WScript.StdOut.WriteLine "First Step..."
WScript.Sleep Int(2000)
Set objShell = CreateObject("Wscript.Shell"): objShell.Run "powershell -nologo -file D:\basic\child.ps1" ,0,true
WScript.StdOut.WriteLine "Script Completed."
WScript.Sleep Int(5000)
powershell script
Write-Host "Some Text Printed"
Start-Sleep -s 2
Atthis point, I like the powershell script to write to vbscript(cscript.exe) console.
I am running vb script as follow.
cscript d:\basic\script.vbs
Is there any work around for this requirement.
As explained in the answer to the question that I linked as a possible duplicate, you can do something like this:
WScript.StdOut.WriteLine "Welcome..."
WScript.StdOut.WriteLine "First Step..."
WScript.Sleep Int(2000)
res = getCommandOutput("powershell -nologo -file D:\basic\child.ps1")
WScript.StdOut.Write res
WScript.StdOut.WriteLine "Script Completed."
WScript.Sleep Int(5000)
Function getCommandOutput(theCommand)
Dim objShell, objCmdExec
Set objShell = CreateObject("WScript.Shell")
Set objCmdExec = objshell.exec(thecommand)
getCommandOutput = objCmdExec.StdOut.ReadAll
end Function

vbscript using Wscript to run a powershell script - need the return from powershell

We are using vbscript in a Classic ASP page and in that vbscript I am calling Powershell using Wscript. I would like to check the return as it is meant to tell me whether the Powershell finished successfully of not. I have a return value in the Powershell script. I've tried both objShell.Run and objShell.Exec and neither one is allowing the Powershell return value through to my ASP page.
My question: how can I get the return values from Powershell?
VBScript follows:
'call PowerShell script with filename and printername and scriptname
strScript = Application("EnvSvcsPSScript")
Set objShell = CreateObject("Wscript.Shell")
dim strCommand
strCommand = "powershell.exe -file " & strScript & " " & strFileName & " " & strPrinterName
Set strPSReturn = objShell.Run(strCommand, 0, true)
response.Write("return from shell: " & strPSReturn.StdOut.ReadAll & "<br>")
response.Write("return from shell: " & strPSReturn.StdErr.ReadAll & "<br>")
Powershell script:
$FileName = $args[0]
$PrinterName = $args[1]
$strReturn = "0^Successful"
"Filename: " + $FileName
"Printer: " + $PrinterName
try
{
get-content $FileName | out-printer -name $PrinterName
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
catch
{
$strReturn = "1^Error attempting to print report."
}
finally
{
}
return $strReturn
THANK YOU!
You can check whether your PowerShell script succeeded. Check out this example.
Powershell script:
$exitcode = 0
try
{
# Do some stuff here
}
catch
{
# Deal with errors here
$exitcode = 1
}
finally
{
# Final work here
exit $exitcode
}
VB Script:
Dim oShell
Set oShell = WScript.CreateObject ("WScript.Shell")
Dim ret
ret = oShell.Run("powershell.exe -ep bypass .\check.ps1", 0, true)
WScript.Echo ret
Set oShell = Nothing
Now if you run the VB script you'll get 0 if the PowerShell script succeeded and 1 otherwise.
However this approach won't let you get exit codes other than 0 or 1.

How to script FTP upload and download

I'm attempting to make a batch file to upload a file to an FTP server.
If I type it in manually it works fine, but when I run the batch file it halts after it's connected... It says:
connected to domain.com.
220 microsoft ftp server
User(domain.com:(none)):
And then nothing else. What is going on here?
Below is my batch file:
ftp www.domainhere.com
user useridhere
passwordhere
put test.txt
bye
pause
It's a reasonable idea to want to script an FTP session the way the original poster imagined, and that is the kind of thing Expect would help with. Batch files on Windows cannot do this.
But rather than doing cURL or Expect, you may find it easier to script the FTP interaction with PowerShell. It's a different model, in that you are not directly scripting the text to send to the FTP server. Instead you will use PowerShell to manipulate objects that generate the FTP dialogue for you.
Upload:
$File = "D:\Dev\somefilename.zip"
$ftp = "ftp://username:password#example.com/pub/incoming/somefilename.zip"
"ftp url: $ftp"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
"Uploading $File..."
$webclient.UploadFile($uri, $File)
Download:
$File = "c:\store\somefilename.zip"
$ftp = "ftp://username:password#example.com/pub/outbound/somefilename.zip"
"ftp url: $ftp"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
"Downloading $File..."
$webclient.DownloadFile($uri, $File)
You need PowerShell to do this. If you are not aware, PowerShell is a shell like cmd.exe which runs your .bat files. But PowerShell runs .ps1 files, and is quite a bit more powerful. PowerShell is a free add-on to Windows and will be built-in to future versions of Windows. Get it here.
Source: http://poshcode.org/1134
Create a command file with your commands.
I.e., file commands.txt:
open www.domainhere.com
user useridhere
passwordhere
put test.txt
bye
Then run the FTP client from the command line:
ftp -s:commands.txt
Note: This will work for the Windows FTP client.
Batch files don't work that way. They don't just "type" everything - they run system commands, in this case ftp, wait for them to return, and run the next command... so in this case, the interpreter is simply waiting for ftp to exit.
If you must use the ftp command, then prepare a script file (for example, commands.txt and run ftp -s:commands.txt.
But using cURL, or a PHP/Perl/Python/whatever script may be a better idea.
I've done this with PowerShell:
function DownloadFromFtp($destination, $ftp_uri, $user, $pass){
$dirs = GetDirecoryTree $ftp_uri $user $pass
foreach($dir in $dirs){
$path = [io.path]::Combine($destination,$dir)
if ((Test-Path $path) -eq $false) {
"Creating $path ..."
New-Item -Path $path -ItemType Directory | Out-Null
}else{
"Exists $path ..."
}
}
$files = GetFilesTree $ftp_uri $user $pass
foreach($file in $files){
$source = [io.path]::Combine($ftp_uri,$file)
$dest = [io.path]::Combine($destination,$file)
"Downloading $source ..."
Get-FTPFile $source $dest $user $pass
}
}
function UploadToFtp($artifacts, $ftp_uri, $user, $pass){
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
foreach($item in Get-ChildItem -recurse $artifacts){
$relpath = [system.io.path]::GetFullPath($item.FullName).SubString([system.io.path]::GetFullPath($artifacts).Length + 1)
if ($item.Attributes -eq "Directory"){
try{
Write-Host Creating $item.Name
$makeDirectory = [System.Net.WebRequest]::Create($ftp_uri+$relpath);
$makeDirectory.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
$makeDirectory.Method = [System.Net.WebRequestMethods+FTP]::MakeDirectory;
$makeDirectory.GetResponse();
}catch [Net.WebException] {
Write-Host $item.Name probably exists ...
}
continue;
}
"Uploading $item..."
$uri = New-Object System.Uri($ftp_uri+$relpath)
$webclient.UploadFile($uri, $item.FullName)
}
}
function Get-FTPFile ($Source,$Target,$UserName,$Password)
{
$ftprequest = [System.Net.FtpWebRequest]::create($Source)
$ftprequest.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$ftprequest.UseBinary = $true
$ftprequest.KeepAlive = $false
$ftpresponse = $ftprequest.GetResponse()
$responsestream = $ftpresponse.GetResponseStream()
$targetfile = New-Object IO.FileStream ($Target,[IO.FileMode]::Create)
[byte[]]$readbuffer = New-Object byte[] 1024
do{
$readlength = $responsestream.Read($readbuffer,0,1024)
$targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -ne 0)
$targetfile.close()
}
#task ListFiles {
#
# $files = GetFilesTree 'ftp://127.0.0.1/' "web" "web"
# $files | ForEach-Object {Write-Host $_ -foregroundcolor cyan}
#}
function GetDirecoryTree($ftp, $user, $pass){
$creds = New-Object System.Net.NetworkCredential($user,$pass)
$files = New-Object "system.collections.generic.list[string]"
$folders = New-Object "system.collections.generic.queue[string]"
$folders.Enqueue($ftp)
while($folders.Count -gt 0){
$fld = $folders.Dequeue()
$newFiles = GetAllFiles $creds $fld
$dirs = GetDirectories $creds $fld
foreach ($line in $dirs){
$dir = #($newFiles | Where { $line.EndsWith($_) })[0]
[void]$newFiles.Remove($dir)
$folders.Enqueue($fld + $dir + "/")
[void]$files.Add($fld.Replace($ftp, "") + $dir + "/")
}
}
return ,$files
}
function GetFilesTree($ftp, $user, $pass){
$creds = New-Object System.Net.NetworkCredential($user,$pass)
$files = New-Object "system.collections.generic.list[string]"
$folders = New-Object "system.collections.generic.queue[string]"
$folders.Enqueue($ftp)
while($folders.Count -gt 0){
$fld = $folders.Dequeue()
$newFiles = GetAllFiles $creds $fld
$dirs = GetDirectories $creds $fld
foreach ($line in $dirs){
$dir = #($newFiles | Where { $line.EndsWith($_) })[0]
[void]$newFiles.Remove($dir)
$folders.Enqueue($fld + $dir + "/")
}
$newFiles | ForEach-Object {
$files.Add($fld.Replace($ftp, "") + $_)
}
}
return ,$files
}
function GetDirectories($creds, $fld){
$dirs = New-Object "system.collections.generic.list[string]"
$operation = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
$reader = GetStream $creds $fld $operation
while (($line = $reader.ReadLine()) -ne $null) {
if ($line.Trim().ToLower().StartsWith("d") -or $line.Contains(" <DIR> ")) {
[void]$dirs.Add($line)
}
}
$reader.Dispose();
return ,$dirs
}
function GetAllFiles($creds, $fld){
$newFiles = New-Object "system.collections.generic.list[string]"
$operation = [System.Net.WebRequestMethods+Ftp]::ListDirectory
$reader = GetStream $creds $fld $operation
while (($line = $reader.ReadLine()) -ne $null) {
[void]$newFiles.Add($line.Trim())
}
$reader.Dispose();
return ,$newFiles
}
function GetStream($creds, $url, $meth){
$ftp = [System.Net.WebRequest]::Create($url)
$ftp.Credentials = $creds
$ftp.Method = $meth
$response = $ftp.GetResponse()
return New-Object IO.StreamReader $response.GetResponseStream()
}
Export-ModuleMember UploadToFtp, DownLoadFromFtp
You can script the ftp command with the -s:filename option. The syntax is just a list of commands to pass to the ftp shell, each terminated by a newline. This page has a nice reference to the commands that can be performed with ftp.
Upload/Download Entire Directory Structure
Using the normal ftp doesn't work very well when you need to have an entire directory tree copied to or from a FTP site. So you could use something like these to handle those situations.
These scripts work with the Windows ftp command and allows for uploading and downloading of entire directories from a single command. This makes it pretty self-reliant when using on different systems.
Basically, they map out the directory structure to be up/downloaded, dump corresponding ftp commands to a file, and then execute those commands when the mapping has finished.
ftpupload.bat
#echo off
SET FTPADDRESS=%1
SET FTPUSERNAME=%2
SET FTPPASSWORD=%3
SET LOCALDIR=%~f4
SET REMOTEDIR=%5
if "%FTPADDRESS%" == "" goto FTP_UPLOAD_USAGE
if "%FTPUSERNAME%" == "" goto FTP_UPLOAD_USAGE
if "%FTPPASSWORD%" == "" goto FTP_UPLOAD_USAGE
if "%LOCALDIR%" == "" goto FTP_UPLOAD_USAGE
if "%REMOTEDIR%" == "" goto FTP_UPLOAD_USAGE
:TEMP_NAME
set TMPFILE=%TMP%\%RANDOM%_ftpupload.tmp
if exist "%TMPFILE%" goto TEMP_NAME
SET INITIALDIR=%CD%
echo user %FTPUSERNAME% %FTPPASSWORD% > %TMPFILE%
echo bin >> %TMPFILE%
echo lcd %LOCALDIR% >> %TMPFILE%
cd %LOCALDIR%
setlocal EnableDelayedExpansion
echo mkdir !REMOTEDIR! >> !TMPFILE!
echo cd %REMOTEDIR% >> !TMPFILE!
echo mput * >> !TMPFILE!
for /d /r %%d in (*) do (
set CURRENT_DIRECTORY=%%d
set RELATIVE_DIRECTORY=!CURRENT_DIRECTORY:%LOCALDIR%=!
echo mkdir "!REMOTEDIR!/!RELATIVE_DIRECTORY:~1!" >> !TMPFILE!
echo cd "!REMOTEDIR!/!RELATIVE_DIRECTORY:~1!" >> !TMPFILE!
echo mput "!RELATIVE_DIRECTORY:~1!\*" >> !TMPFILE!
)
echo quit >> !TMPFILE!
endlocal EnableDelayedExpansion
ftp -n -i "-s:%TMPFILE%" %FTPADDRESS%
del %TMPFILE%
cd %INITIALDIR%
goto FTP_UPLOAD_EXIT
:FTP_UPLOAD_USAGE
echo Usage: ftpupload [address] [username] [password] [local directory] [remote directory]
echo.
:FTP_UPLOAD_EXIT
set INITIALDIR=
set FTPADDRESS=
set FTPUSERNAME=
set FTPPASSWORD=
set LOCALDIR=
set REMOTEDIR=
set TMPFILE=
set CURRENT_DIRECTORY=
set RELATIVE_DIRECTORY=
#echo on
ftpget.bat
#echo off
SET FTPADDRESS=%1
SET FTPUSERNAME=%2
SET FTPPASSWORD=%3
SET LOCALDIR=%~f4
SET REMOTEDIR=%5
SET REMOTEFILE=%6
if "%FTPADDRESS%" == "" goto FTP_UPLOAD_USAGE
if "%FTPUSERNAME%" == "" goto FTP_UPLOAD_USAGE
if "%FTPPASSWORD%" == "" goto FTP_UPLOAD_USAGE
if "%LOCALDIR%" == "" goto FTP_UPLOAD_USAGE
if not defined REMOTEDIR goto FTP_UPLOAD_USAGE
if not defined REMOTEFILE goto FTP_UPLOAD_USAGE
:TEMP_NAME
set TMPFILE=%TMP%\%RANDOM%_ftpupload.tmp
if exist "%TMPFILE%" goto TEMP_NAME
echo user %FTPUSERNAME% %FTPPASSWORD% > %TMPFILE%
echo bin >> %TMPFILE%
echo lcd %LOCALDIR% >> %TMPFILE%
echo cd "%REMOTEDIR%" >> %TMPFILE%
echo mget "%REMOTEFILE%" >> %TMPFILE%
echo quit >> %TMPFILE%
ftp -n -i "-s:%TMPFILE%" %FTPADDRESS%
del %TMPFILE%
goto FTP_UPLOAD_EXIT
:FTP_UPLOAD_USAGE
echo Usage: ftpget [address] [username] [password] [local directory] [remote directory] [remote file pattern]
echo.
:FTP_UPLOAD_EXIT
set FTPADDRESS=
set FTPUSERNAME=
set FTPPASSWORD=
set LOCALDIR=
set REMOTEFILE=
set REMOTEDIR=
set TMPFILE=
set CURRENT_DIRECTORY=
set RELATIVE_DIRECTORY=
#echo on
This script generates the command file then pipes the command file to the ftp program, creating a log along the way. Finally print the original bat file, the command files and the log of this session.
#echo on
#echo off > %0.ftp
::== GETmy!dir.bat
>> %0.ftp echo a00002t
>> %0.ftp echo iasdad$2
>> %0.ftp echo help
>> %0.ftp echo prompt
>> %0.ftp echo ascii
>> %0.ftp echo !dir REPORT.CP1C.ROLLEDUP.TXT
>> %0.ftp echo get REPORT.CP1C.ROLLEDUP.TXT
>> %0.ftp echo !dir REPORT.CP1C.ROLLEDUP.TXT
>> %0.ftp echo *************************************************
>> %0.ftp echo !dir CONTENT.CP1C.ROLLEDUP.TXT
>> %0.ftp echo get CONTENT.CP1C.ROLLEDUP.TXT
>> %0.ftp echo !dir CONTENT.CP1C.ROLLEDUP.TXT
>> %0.ftp echo *************************************************
>> %0.ftp echo !dir WORKLOAD.CP1c.ROLLEDUP.TXT
>> %0.ftp echo get WORKLOAD.CP1C.ROLLEDUP.TXT
>> %0.ftp echo !dir WORKLOAD.CP1C.ROLLEDUP.TXT
>> %0.ftp echo *************************************************
>> %0.ftp echo !dir REPORT.TMMC.ROLLEDUP.TXT
>> %0.ftp echo get REPORT.TMMC.ROLLEDUP.TXT
>> %0.ftp echo !dir REPORT.TMMC.ROLLEDUP.TXT
>> %0.ftp echo *************************************************
>> %0.ftp echo !dir CONTENT.TMMC.ROLLEDUP.TXT
>> %0.ftp echo get CONTENT.TMMC.ROLLEDUP.TXT
>> %0.ftp echo !dir CONTENT.TMMC.ROLLEDUP.TXT
>> %0.ftp echo **************************************************
>> %0.ftp echo !dir WORKLOAD.TMMC.ROLLEDUP.TXT
>> %0.ftp echo get WORKLOAD.TMMC.ROLLEDUP.TXT
>> %0.ftp echo !dir WORKLOAD.TMMC.ROLLEDUP.TXT
>> %0.ftp echo quit
ftp -d -v -s:%0.ftp 150.45.12.18 > %0.log
type %0.bat
type %0.ftp
type %0.log
I was having a similar issue - like the original poster, I wanted to automate a file upload, but I couldn't figure out how. Because this is on a register terminal at my family's store, I didn't want to install PowerShell (although that looks like an easy option) and I just wanted a simple .bat file to do this.
This is pretty much what grawity and another user said; I'm new to this stuff, so here's a more detailed example and explanation (thanks also to How to Automate FTP Uploads from the Windows Command Line who explains how to do it with just one .bat file).
Essentially you need two files - one .bat and one .txt. The .bat tells ftp.exe what switches to use. The .txt gives a list of commands to ftp.exe. In the text file put this:
username
password
cd whereverYouWantToPutTheFile
lcd whereverTheFileComesFrom
put C:\InventoryExport\inventory.test (or your file path)
bye
Save that wherever you want. In the BAT file put:
ftp.exe -s:C:\Windows\System32\test.txt destinationIP
pause
Obviously change the path after the -s: to wherever your text file is. Take out the pause when you're actually running it - it's just so you can see any errors. Of course, you can use "get" or any other ftp command in the .txt file to do whatever you need to do.
I'm not positive that you need the lcd command in the text file. Like I said, I'm new to using command line for this type of thing, but this is working for me.
I had this same issue, and solved it with a solution similar to what Cheeso provided.
"doesn't work, says password is srequire, tried it a couple different ways "
Yep, that's because FTP sessions via a command file don't require the username to be prefaced with the string "user". Drop that, and try it.
Or, you could be seeing this because your FTP command file is not properly encoded (that bit me, too). That's the crappy part about generating a FTP command file at runtime. PowerShell's out-file cmdlet does not have an encoding option that Windows FTP will accept (at least not one that I could find).
Regardless, as doing a WebClient.DownloadFile is the way to go.
Try manually:
$ ftp www.domainhere.com
> useridhere
> passwordhere
> put test.txt
> bye
> pause