How can I return multiple values from a powershell script to the batch file that is calling it? - powershell

How can I return multiple values from a powershell script to the batch file that is calling it?
I have a powershell script that returns multiple values. I want to call it from a batch file and have each individual value go into an individual variable in the batch file.
Only been able to return one value
The powershell code (pstest.ps1):
$p1=11
$p2=22
$p3=33
exit
The batch file:
powershell .\pstest.ps1
:: now I'd like to get those 3 returned values
:: into 3 individual variables so that I can do something like this:
#echo First is %p1%, Second is %p2%, Third is %p3%
So, it should display this:
First is 11, Second is 22, Third is 33

In the exact same way you get several values from any application: one per line...
#echo off
setlocal EnableDelayedExpansion
set "i=0"
for /F "delims=" %%a in ('powershell "$p1=11; $p2=22; $p3=33; $p1; $p2; $p3"') do (
set /A i+=1
set "p!i!=%%a"
)
echo First is %p1%, Second is %p2%, Third is %p3%
I suggest you to read this answer about arrays...

For a more flexible solution using delayed expansion that doesn't require making the PowerShell script output all values on a single line and works with a variable number of outputs, see Aacini's helpful answer.
Assuming that the number of values is fixed and known in advance:
Make the PowerShell script output a single line containing all the values with a known separator.
In the batch file, use for /f "delims=... tokens=..." to capture the values in discrete loop variables, which you can assign to regular variables.
A simplified example that uses a PowerShell command to produce a single-line output with 3 values separated by | (to apply this to your scenario, use -File with your *.ps1 file instead of -c (-Command) with a command string):
#echo off
for /f "delims=| tokens=1,2,3" %%a in ('powershell -c " '11|22|33' "') do set "p1=%%a" & set "p2=%%b" & set "p3=%%c"
:: echo the values of the newly created variables
echo p1: [%p1%]
echo p2: [%p2%]
echo p3: [%p3%]
The above yields:
p1: [11]
p2: [22]
p3: [33]

Apart from using environment variables, you could try something like this base on SachaDee's answer here:
PS:
function get-multiplereturnvalues {
"11"
"22"
"33"
}
get-multiplereturnvalues
BATCH/CMD
#echo First is %p1%, Second is %p2%, Third is %p3%
for /f "delims=" %%a in ('powershell .\multi.ps1') do #echo "$Value=%%a"
Which outputs:
"$Value=11"
"$Value=22"
"$Value=33"

Within the PowerShell script I‘d create a temp file with the results. Within the batch file, after calling the PowerShell script, I‘d parse that temp file with FOR /F in order to get the results and set environment variables as required.

Related

Powershell in Batch file: Errors and Commands not executing

Below you can find the content of the two example text files I will use, example1.txt and obf_example1.txt. The latter one contains the string of example1.txt at the end of the file but has some obfuscated strings before.
example1.txt:
adasdkasdaksdasdkjlasdjasndjasd.
obf_example1.txt:
ŠxpÃÒ²Ø-Gêÿ ój"f>ïí H€À(ø4$/+#6Ni9Pvü¶ |CF CÀ¾ý~ª-°à9ÉOÿ V[o¦.E…-Š ƒ9Ú\žê*D´ß()^“£¹ìÅjXÑÍ¥â(¨µ×d'«P|I*èSººº&)Ø|̉ òÔ®¥Ô$LÁ:9ŠLá{¶nZÒبNÙÀØŒ‹0õ´Sék›áÇÉîÆbËF§BЄƒöZKaÒR ²°ÅšDn?+¶()IªP›$ÇEv©¡k€[ßè¨×q-Ëk!µTóPA²—: A ?ÉEEEGÐJúÌ©ÒWµHB¡aäXû|ÓË BPÁwr„Ûi¥åܺÈQ÷ORàSb,Šv¢D ,Žb’(2 öb¢wtKzíĦ#ï¯u©²Ù aîR隬ëÌTbà÷¥3ÄtSGì´R$)X Šù
'¹¨D³ÞeOK3!{·‹¦cäиNÅô:Na1žAÇ1ø8 &Fuôë %¸T¯_òMå†C"ý¤F ™º„Iµºí4Ü¡ˆc!ì•+3 ‰‹M K#JÁ«8¢bsL†!Ù“à­šn·öMå•Œ&ýèvÀ}¨?¦hùÊò(É#Žf~5‰‘qØçþƒ‰Å²ÓÖÊJU•âNWÁ«L¼Y”$G¢ßè&§ÖÉØŒS‘WàË„°SØW Ð¨´_è%‚Å¢ø.ãÃð”#X^þ*1þ‚q85¡lÒ‚Ò>‘¸ÿ £ôQôz#ø¤ÎõÚªï|Xö%;åÍËûGú+îUƒö³‰›p U±Ò ðtÜGÜÿ  ð,åXÿ k8È I”ÿ “½¿Ð`¨u5=SÓqyFÈ É8ôã¨ð£è6’H#lÄI10‚Ö§ÑdµÖ?t¡]D†9Zj,¥EɺÜEq¤#,ìn—¢º‚´€bc·ú¨Lû£ÿ Ó×ÿÙ||adasdkasdaksdasdkjlasdjasndjasd.
When I ran the following powershell command for example.txt in a batch file, it works and I get the output of example.txt:
#echo off
for /f "delims=" %%a in ('powershell Get-Content .\example.txt') do set _output=%%a
echo %_output%
adasdkasdaksdasdkjlasdjasndjasd
Good so far.
However, when I ran the above powershell command for obf_example1.txt, it does not work and I get the following error message:
'¹¨D³ÃzeOK3!{·â?¹Â¦cäÃ?Â?¸Â?NÃ.ô:Na1žAÃ╬1ø8
The command "FuôëÂ" is either misspelled or could not be found.
The command "ýèvÃ?}¨?¦hùÃSÂ?ò" is either misspelled or could not be found.
Why? Never mind I thought: As I am only interested in the last n characters both in example1.txt and obf_example1.txt accordingly, my idea was to extract the last n characters and check if I can see the output of obf_example1.txt then. To check if my idea works, I run the following command for example1.txt to get the last 4 characters as an example:
#echo off
for /f "delims=" %%a in ('powershell $a=Get-Content .\example.txt; $a.substring^(0,$a.length-4^)') do set _output=%%a
echo.%_output%
It doesn't show me anything though. %_output% seems to be empty. How to fix that? And will the fixed version work for obf_example1.txt as well so that I get an output there instead of the above error message?
Apparently, the piece of text you are after is behind the ||.
With PowerShell you can easily get that by using
((Get-Content 'D:\Test\obf_example1.txt' -Raw) -split '\|\|')[-1]
Returns
adasdkasdaksdasdkjlasdjasndjasd
Isn't htis what you want?
You could try reading the last 4 bytes, if you really are taking text characters from what is clearly not a text file. (My guess is that it is text hidden inside a binary file, probably a graphic file).
#For /F Delims^=^ EOL^= %%G In (
'%__AppDir__%WindowsPowerShell\v1.0\powershell.exe -NoP^
"$f=[IO.File]::OpenRead('C:\Users\Ferit\Desktop\obf_example1.txt');"^
"$f.Seek(-4,[System.IO.SeekOrigin]::End)|Out-Null;$buffer=new-object Byte[] 4;"^
"$f.Read($buffer,0,4)|Out-Null;$f.Close();"^
"[System.Text.Encoding]::UTF8.GetString($buffer)"')Do #Set "_output=%%G"
#Set _output 2>NUL&&Pause
Don't forget to modify the text file path, (on line 3), and the three instances of 4 if you want more or less bytes. The last line is included just to show you the output, (you would obviously replace that with your own code).
The following works for me to get the "clear-text" part after || (from your example):
for /f "delims=" %%a in (.\obf_example1.txt) do set "_output=%%a"
set _output
echo testing last 10: %_output:~-10%
set "_output=%_output:*||=%"
set _output
echo %_output%
(it might not work with different encodings of the text file)
(Consider Powershell - cmd has a limit on line length and can easily be overwhelmed)

Use Variable insted of Get-Date in powershell

I am using PowerShell "(Get-Date).AddDays(-7).ToString('ddMMyyyy')" in batch script.
I want to use a variable instead of Get-Date function. Is it possible?
ADate is the variable name!
Edited:
As suggested, my script is:
For /F UseBackQ %%A In (
`PowerShell "(Get-Date).AddDays(-7).ToString('ddMMyyyy')"`
) Do Set "Freq=%%A"
Adate is simple string which comes from the file name, and has a value like 16112016.
You need to use a for loop to get the output of an external command in a batch variable:
#echo off
for /f "usebackq tokens=*" %%d in (`powershell "..."`) do set "adate=%%d"
echo %adate%
The usebackq and backticks are just so you don't need to escape the nested single quotes in your command string.
Ok. I got my crystal ball and asked it: "What is the solution here?", and it replied: "Change
PowerShell "(Get-Date).AddDays(-7).ToString('ddMMyyyy')"
by
PowerShell "(Get-Date -Date '!Adate:~4!-!Adate:~2,2!-!Adate:~0,2!').AddDays(-7).ToString('ddMMyyyy')"
", but I have no idea what it is talking about! ;)
You can store the output of the powerShell command into a file and then read that file after that delete that temporary file.
PowerShell "(Get-Date).AddDays(-7).ToString('ddMMyyyy')" >temp.txt
set /p myVarDate= < date_Shell.txt
echo Date from Shell %myVarDate%
del temp.txt
The following takes the last modified time from a known file's properties and creates a variable with a date seven days earlier, (obviously changing C:\Test\TestFile.ext as necessary):
For /F UseBackQ %%A In (
`PowerShell "((gi 'C:\Test\TestFile.ext').LastWriteTime).AddDays(-7).ToString('ddMMyyyy')"`
) Do Set "ADate=%%A"
Edit
The following example takes a date string with a known format, (in this case provided in two variables). It then converts that string to a date object, subtracts seven days and sets it back to a string in the new %ADate% variable:
#Echo Off
Set "DateStr=16112016"
Set "DFormat=ddMMyyyy"
For /F UseBackQ %%A In (`Powershell^
"([datetime]::ParseExact('%DateStr%','%DFormat%', [System.Globalization.CultureInfo]::CurrentCulture)).AddDays(-7).ToString('%DFormat%')"
`) Do Set "ADate=%%A"
Echo(%ADate%
Timeout -1

Strip CMD output to just show directories

I'm currently trying to replace a powershell script with a cmd script as it's more suitable for what is trying to be done.
In Powershell I'm using this bit of code to return a list of personal folder directories on the computer
$Name = [Environment]::UserName
get-item HKCU:\software\Microsoft\Office\15.0\Outlook\Search\Catalog - ErrorAction SilentlyContinue | select -expandProperty property | Out-File Z:\global\pst\PowershellOutput\$Name.txt -append
This does what I want and outputs a list of directories like so
H:\PST\My Outlook Data File 1.pst
H:\PST\My Outlook Data File 2.pst
C:\PST\My Outlook Data File 3.pst
However when I run this line to extract the registry key
regedit.exe /e Z:\global\battest\%username%.txt "HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Search\Catalog"
I get an output with lots of unnecessary data
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Search\Catalog]
"H:\\PST\\My Outlook Data File 1.pst"=hex:0c,01,00,00,00,00,00,00
"H:\\PST\\My Outlook Data File 2.pst"=hex:f8,00,00,00,00,00,00,00
"C:\\PST\\My Outlook Data File 3.pst"=hex:ac,02,00,00,00,00,00,00
This data is passed onto another program* so a work-around could be to use data within the "" marks however it also has the double backslashes which makes the data awkward to pass on.
Is there a better way to grab these values within CMD or perhaps a parameter which I've missed which just shows the directories?
-Sorry for not including this before however this program is not a CMD program, it's visual basic
While I don't have Office installed to test, this should get the work done
#echo off
setlocal enableextensions disabledelayedexpansion
set "HKCU=&H80000001"
set "subKey=software\Microsoft\Office\15.0\Outlook\Search\Catalog"
>> "Z:\global\pst\PowershellOutput\%username%.txt" (
for /f "tokens=2 delims={" %%a in ('
wmic
/NameSpace:\\root\default
Class StdRegProv
Call EnumValues
hDefKey^="%HKCU%"
sSubKeyName^="%subkey%"
2^>nul
^| find "sNames = {"
') do for %%b in (%%a) do (
for /f delims^=^" %%c in ("%%~b") do echo(%%~fc
)
)
It uses wmic to retrieve the list of values defined under the indicated key and filter the output to only retrieve the line with the names of those values in the form sNames = {"v1", "v2", "v3"}.
The { is used to separate the start of the line from the list of values (%%a) , and this list is iterated (%%b) to get each value. The last element in the list includes an ending } that needs to be removed, this is handled by %%c using the quotes in the value as delimiters.
The equivalent vbs version could be
Option Explicit
Const HKEY_CURRENT_USER = &H80000001
Const ForAppending = 8
Const SUB_KEY = "software\Microsoft\Office\15.0\Outlook\Search\Catalog"
Const OUTPUT_PATH = "Z:\global\pst\PowershellOutput"
Dim fso, shell
Set fso = WScript.CreateObject("Scripting.FileSystemObject")
Set shell = WScript.CreateObject("WScript.Shell")
Dim values
Call GetObject( _
"winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv" _
).EnumValues( _
HKEY_CURRENT_USER, SUB_KEY, values _
)
Dim outputFile
Set outputFile = fso.OpenTextFile( _
fso.BuildPath( _
OUTPUT_PATH, shell.ExpandEnvironmentStrings("%username%") & ".txt" _
) _
, ForAppending _
, True _
)
Dim value
If Not IsNull(values) Then
For Each value In values
Call outputFile.WriteLine(fso.GetAbsolutePathName( value ))
Next
End If
Call outputFile.Close()
You would probably need to use the REG QUERY command. In a single bat file it would like like:
#echo OFF
for /f "usebackq tokens=1-3" %%A in (`REG QUERY "HKCU\Software\Microsoft\Office\15.0\Outlook\Search\Catalog"`) do (
set ValueName=%%A
)
echo %ValueName% > Z:\global\battest\%username%.txt
Got this from How can I get the value of a registry key from within a batch script?
Here is a batch code to get the list of *.pst files (not directories) which work independent on number of *.pst files and their file names as long as the file names end with .pst.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /F "skip=1 delims=" %%# in ('%SystemRoot%\System32\reg.exe query HKCU\Software\Microsoft\Office\15.0\Outlook\Search\Catalog 2^>nul') do (
set "RegData=%%#"
set "DelData=!RegData:*.pst=!"
if not "!DelData!" == "!RegData!" call :OutputFileName
)
endlocal
goto :EOF
:OutputFileName
set "RegData=!RegData:%DelData%=!"
echo !RegData:~4!
goto :EOF
*.pst files are Outlook Personal Storage files which are container files for emails, contacts, ...
The command FOR runs console application REG to query all values of registry key:
HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Search\Catalog
The output on Windows Vista and later Windows versions is for the example:
HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Search\Catalog
H:\PST\My Outlook Data File 1.pst REG_SZ whatever the
H:\PST\My Outlook Data File 2.pst REG_SZ data value is
C:\PST\My Outlook Data File 3.pst REG_SZ of each value
There is output the registry key and next the registry values each indented with 4 spaces and with 4 spaces between value name and value type and 4 spaces between value type and data value.
But on Windows XP the output is for the example:
! REG.EXE VERSION 3.0
HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Search\Catalog
H:\PST\My Outlook Data File 1.pst REG_SZ whatever the
H:\PST\My Outlook Data File 2.pst REG_SZ data value is
C:\PST\My Outlook Data File 3.pst REG_SZ of each value
So there are first two header lines before the registry key. The registry values are also indented with 4 spaces. But there are 1 or more tabs instead of spaces between the value name and the value type and there are also 1 or more tabs instead of spaces between value type and data value.
Note: I don't know if REG_SZ is the right registry value type. I don't have this registry key at all in my Windows registry and therefore just added the 3 registry values as string values with the dummy strings.
So the tricky part is how to get the registry value name containing also spaces to get just the name of the *.pst files independent on file name.
This is done by assigning each output line with the exception of first line because of skip=1 to environment variable RegData and get next from this line everything after .pst assigned to environment variable DelData.
If the line contains .pst in any case at all resulting in value of DelData being not equal value of RegData, the subroutine OutputFileName is called for further processing and printing the file name of the *.pst file.
In the subroutine OutputFileName first the string right of *.pst file name is removed from RegData using a string substitution. Next *.pst file name is output without the 4 indent spaces at beginning.
If the number of indent spaces would not be always exactly 4 spaces, the line echo !RegData:~4! could be replaced by following code to work independent on number of indent spaces/tabs.
for /F "tokens=1*" %%I in ("%RegData%") do (
if "%%J" == "" (
echo %%I
) else (
echo %%I %%J
)
)
The usage of REG instead of REGEDIT has the advantage that no elevated privileges of an administrator is necessary for running this batch code.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
echo /?
for /?
goto /?
reg /?
reg query /?
set /?
setlocal /?
Nothing is output if the registry key does not exist. The error message output to handle STDERR is suppressed here by redirecting it with 2^>nul to device NUL. See the Microsoft article Using command redirection operators for details. The redirection operator > must be escaped here with caret character ^ to be interpreted as literal character on parsing the FOR command line and being interpreted as redirection operator on execution of the REG command line by FOR.

MS DOS edit a file

I am writing a batch script which I wish to open a file and then change the second line of it. I want to find the string "cat" and replace it with a value that I have SET i.e. %var% . I only want this to happen on the second line (or for the first 3 times). How would you go about doing this?
I just solve it myself. It will lookup var on line two only.
#echo OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET filename=%1
set LINENO=0
for /F "delims=" %%l in (%filename%) do (
SET /A LINENO=!LINENO!+1
IF "!LINENO!"=="2" ( call echo %%l ) ELSE ( echo %%l )
)
But I prefer using cscript (vbscript or even jscript).
First of all, using a batch file to achieve this, is messy (IMHO). You will have to use an external tool anyway to do the string replacement. I'd use some scripting language instead.
If you really want to use a batch, this will get you started.
This would be ugly to do with native batch scripting. I would either
Do this in VBScript. If you really need this in a batch file, you can call the VBScript file from the batch script. You can even pass in %var% as an argument to the VBScript.
Use a sed script. There are windows ports of Unix commands like GnuWin32, GNU Utilities for Win32 (I use these), or Cygwin.
I would create a script that would:
scan the input file
write to a second output file
delete the input
rename the output
As far as the dos commands to parse, I did a Google Search and came up with a good starting point:
#echo off
setlocal enabledelayedexpansion
set file=c:\file.txt
set output=output.txt
set maxlines=5000
set count=0
for /F "tokens=* usebackq" %%G in ("%file%") do (
if !count!==%maxlines% goto :eof
set line=%%G
set line=!line:*000000000000=--FOUND--!
if "!line:~0,9!"=="--FOUND--" (
echo %%G>>"%output%"
set /a count+=1
)
)
(Stolen from teh Intarwebnet)

Search and replace with a batch file

I am writing a batch file script using Windows command and want to change each occurrence of some blank space with "," What is the simplest way to do that?
If your users are a list of words on one line in a text file, separated by spaces, eg:
one two three four
Create a batch file SpaceToComma.bat as follows:
#echo off
setlocal
for /F "tokens=* delims= " %%a in (%1) do #set data=%%a
echo %data: =,%
endlocal
Then run it, you'll get the words separated by commas. Is this what you want?
C:\>SpaceToComma.bat data.txt
one,two,three,four
If you have a multi-line file, then this will do it:
data.txt
one two three four
five six seven
SpaceToComma.bat
#echo off
setlocal
for /F "tokens=* delims= " %%a in (%1) do #call :processaline %%a
endlocal
goto :eof
:processaline
setlocal
set data=%*
echo %data: =,%
endlocal
goto:eof
Output
C:\>SpaceToComma.bat data.txt
one,two,three,four
five,six,seven
(There's probably a clever way of doing this without the subroutine, using the !data! syntax for delayed variable expansion, but I couldn't get it to work with the substitution syntax.)
If this is not what you want, then please explain and I can try to help.
(PS: I delight in using batch files where people insist it can't be done.)
You could download sed.exe from http://unxutils.sourceforge.net/ and run this command:
sed -e "s/ /,/" infile.txt >outfile.txt
Well it will depend a lot on how you are getting the data, but you may be able to finagle something with a For-Do construct. Make the field seperator be a space then build the string back in the do with a , between each token. Might I suggest a more robust scripting language? You should have VBScript on just about any relatively modern windows bow.