I turn in circles and after much research, I seek your expertise on this small case. I can send the result of a PS1 script to a text file, but not to a batch file variable.
the script
Param(
[string]$Fic
)
$EmplacementFichier = [string]
$EmplacementFichier = "$Fic"
$MonFichier = get-content -totalcount 1 $EmplacementFichier
$Resultat = $MonFichier.SubString(92,12)
$RnmFic = "EXANTE_$resultat.REPRESTI.txt"
rename-item $EmplacementFichier -newname $RnmFic
Write-Output $RnmFic
Launched from a batch file:
powershell D:\Rnm-Exante.ps1 -fic "%NOMFIC%" > %Fichier%
It creates a file "%Fichier%" at the location of the script, but does not provide the batch variable.
from batch, you can read the file:
<file.ext set /p "var="`
or you can get the output of your powershell script directly (do not redirect to a file in this case):
for /f "delims=" %%a in ('powershell D:\Rnm-Exante.ps1 -fic "%NOMFIC%"') do set "var=%%a"
Related
I have several files inside different folders and I'm trying to copy them to another different folder.
For example:
[source] [dest]
C:\Users\Downloads\a\a.txt C:\Users\Downloads\222\a.txt
C:\Users\Downloads\b\b.jpg C:\Users\Downloads\333\b.jpg
C:\Users\Downloads\xyz\c.exe C:\Users\Downloads\yyy\c.exe
...
How I could create a script to be executed on cmd and using robocopy copy all files at once?
My attempt:
#echo off
set obj[0].source="C:\Users"
set obj[0].dest="C:\Users\a"
set obj[0].file="x.txt"
set obj[1].source="C:\Users"
set obj[1].dest="C:\Users\b"
set obj[1].file="y.txt"
FOR /L %%i IN (0 1) DO (
call echo source = %%obj[%%i].source%%
call echo dest = %%obj[%%i].dest%%
robocopy %%obj[%%i].source%% %%obj[%%i].dest%% %%obj[%%i].file%%
)
pause
Error:
2022/12/22 21:03:36 ERROR 2 (0x00000002) Accessing Source Directory C:\Users\%obj[0].source%\
The system cannot find the file specified.
Press any key to continue . . .
The path is wrong, whats happening?
With Powershell you can do like this :
$Paths=#{
"C:\Users\Downloads\a\a.txt"="C:\Users\Downloads\222\a.txt"
"C:\Users\Downloads\b\b.jpg"="C:\Users\Downloads\333\b.jpg"
"C:\Users\Downloads\xyz\c.exe"="C:\Users\Downloads\yyy\c.exe"
}
$Paths.GetEnumerator() | ForEach-Object {write-host Robocopy $_.Key $_.Value}
And the result is like this one :
Robocopy C:\Users\Downloads\xyz\c.exe C:\Users\Downloads\yyy\c.exe
Robocopy C:\Users\Downloads\a\a.txt C:\Users\Downloads\222\a.txt
Robocopy C:\Users\Downloads\b\b.jpg C:\Users\Downloads\333\b.jpg
Further reading : Read all items in a PowerShell hash table with a loop
EDIT : Tested with xcopy
$Paths=#{
"E:\Batch\SpeedTest\Shortcut-PS.bat"="E:\temp\a\"
"E:\Batch\SpeedTest\SpeedTest_Hackoo_Ookla.bat"="E:\temp\b\"
"E:\Batch\SpeedTest\OLD_SpeedResult.txt"="E:\temp\c\"
}
$Paths.GetEnumerator() | ForEach-Object {xcopy /D $_.Key $_.Value}
Results :
E:\Batch\SpeedTest\SpeedTest_Hackoo_Ookla.bat
1 fichier(s) copi‚(s)
E:\Batch\SpeedTest\OLD_SpeedResult.txt
1 fichier(s) copi‚(s)
E:\Batch\SpeedTest\Shortcut-PS.bat
1 fichier(s) copi‚(s)
I have the following code in command shell code.
SET MYdir=%NewPath%\%CUST%\SuppliesTypes
SET "MYsCount=1"
SET /p MYsCount="Number of MYs in project? (default: %MYSCount%): "
for /L %%a in (1,1,%MYsCount%) do (
SET /p MYNums="Enter %%a MY Number: "
call md "%MYdir%\MY_%%MYNums%%"
)
SET "MYsCount="
However, I am converting my code from CMD to PowerShell. I do not fully understand the correct way to convert over. Here might be how it should be done, but it's not working as it just jumps right through.
SET MYdir=%NewPath%\%CUST%\Product
SET "MYsCount=1"
SET /p MYsCount="Number of MYs in project? (default: %MYSCount%): "
For ($MYsCount = 1; $MYsCount -eq 10; $MYsCount++){
SET /p MyNums="Enter %%a Product Numbers: "
CALL MD "%MYdir%\%CUST%\Product_%%"
}
SET "$MYsCount="
I've looked at the following sites and articles:
PowerShell Basics: Programming With Loops (Helped validate)
How to do a forloop in a Django template? (Didn't really help)
Windows PowerShell Cookbook, 3rd Edition (Page 170)
I am running this code inside a While-Loop.
Thanks for your help!
You have an interesting amalgam of batch file and powershell there in your second code block. It is hard to read when some things are one language and some things are another. Let's see if we can't get it all into PowerShell here.
$MYdir = "$NewPath\$CUST\Product"
$MYsCount = 1
$UserMYsCount = Read-Host "Number of MYs in project? (default: $MYSCount): "
If([string]::IsNullOrEmpty($UserMYsCount){
$UserMYsCount = $MYsCount
}
For ($i = 1; $i -le $UserMYsCount; $I++){
$MyNums = Read-Host "Enter $i Product Numbers: "
New-Item -Path "$MYdir\MY_$MyNums" -ItemType Directory
}
I believe the issue is coming from how you are declaring your variables. SET creates the variables as environment variables that powershell does not access natively. Below is how I would write up your section of code:
$MYDir = "$env:NewPath\$env:CUST\SuppliesTypes"
$MYsCount = 1
$MYsCount = read-host -prompt "Number of MYs in project? (default: $MYSCount): "
foreach ($a in 0..$MYsCount){
$MYNums = Read-Host -Prompt "Enter $a Product Numbers: "
New-Item -Path "$MYDir\MY_$MYNums" -ItemType Directory
}
$MYsCount = $null
I used a foreach loop instead of a normal for loop because you were incrementing by one each time and I have noticed a small performance gain from using foreach when the step is not complicated. 0..$variable is a short hand for using each number from 0 to the declared variable.
If you did want to use a for loop as you mentioned then you could use:
For ($MYsCount = 1; $MYsCount -eq 10; $MYsCount++){
as you had expected. This loop will only stop if the $MYsCount variable equals 10 though so if someone set the variable to something above 10 it would run indefinitely.
I'm trying to make a batch file that reads a txt file "ayylmao.txt" and find a specific word "hello" and replaces it with "xello".
The thing is that the "ayylmao.txt" contains specific characters.
Ayylmao.txt looks something like this:
‹‹R‹Ę‹/M‹;Ču‹č˙˙˙‹‹#‰‹‹#CëC;Đu‹čq˙˙˙‹‹#C‹D$‰;7u®‹Ó‹Ćčúţ˙˙„Ŕu3Ŕ‰YZ]_^[ĂŤ# SVWUÄđ‰$‹ô‹‰D$‹
‹‹#;Č‚† ‹Ř‹>_‹ůz;ßrv;Ču!‹B‹A‹B‹)B‹x uV‹čđţ˙˙ëM‹Ř‹>_‹ůz;ßu
‹B‹)Bë3‹Z‰\$‹>‹‹.}+ű‰|$+Č‹‰HŤT$‹čMţ˙˙„Ŕu3 hello Ŕë°ë‹‹ ‰‹;D$…Y˙˙˙3ŔÄ]_^[ĂSVW‹Ú‹đţ }ľ ëĆ˙˙ ć ˙˙‰sjh Vj
You can see the "hello" word in the last line. I want the batch to go to the process and give me a ayylmao1.txt that looks like this:
‹‹R‹Ę‹/M‹;Ču‹č˙˙˙‹‹#‰‹‹#CëC;Đu‹čq˙˙˙‹‹#C‹D$‰;7u®‹Ó‹Ćčúţ˙˙„Ŕu3Ŕ‰YZ]_^[ĂŤ# SVWUÄđ‰$‹ô‹‰D$‹
‹‹#;Č‚† ‹Ř‹>_‹ůz;ßrv;Ču!‹B‹A‹B‹)B‹x uV‹čđţ˙˙ëM‹Ř‹>_‹ůz;ßu
‹B‹)Bë3‹Z‰\$‹>‹‹.}+ű‰|$+Č‹‰HŤT$‹čMţ˙˙„Ŕu3 xello Ŕë°ë‹‹ ‰‹;D$…Y˙˙˙3ŔÄ]_^[ĂSVW‹Ú‹đţ }ľ ëĆ˙˙ ć ˙˙‰sjh Vj
You can see that "hello" is now "xello".
I found this batch file that replaces a word from a text file:
#echo off
REM -- Prepare the Command Processor --
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEDELAYEDEXPANSION
if "%~1"=="" findstr "^::" "%~f0"&GOTO:EOF
for /f "tokens=1,* delims=]" %%A in ('"type %3|find /n /v """') do (
set "line=%%B"
if defined line (
call set "line=echo.%%line:%~1=%~2%%"
for /f "delims=" %%X in ('"echo."%%line%%""') do %%~X
) ELSE echo.
)
This code works for files that don't have specific characters very good if use it like this:
code.bat "hello" "xello" "ayylmao.txt">"ayylmao1.txt"
This code only types in ayylmao1.txt few special characters but replaces hello. I want all the special characters typed in there.
I made it like this:
chcp 1252
code.bat "hello" "xello" "ayylmao.txt">"ayylmao1.txt"
But it didn't work. It worked just like the first code.
If there is a way in PowerShell to do this I'd be glad to hear it.
What you have there looks like a binary file, not a text file, despite the extension. Batch is no good for editing binary files. In PowerShell it's doable, but you need to resort to working with the data bytes instead of simple text.
This is a basic example that will find the first occurrence of the string "hello" in your file and replace it with "xhello":
$f = 'C:\path\to\ayylmao.txt'
$stext = 'hello'
$rtext = [char[]]'xhello'
$len = $stext.Length
$offset = $len - 1
$data = [IO.File]::ReadAllBytes($f)
# find first occurrence of $stext in byte array
for ($i=0; $i -lt $data.Count - $offset; $i++) {
$slice = $data[$i..($i+$offset)]
if (-join [char[]]$slice -eq $stext) { break }
}
# Once you know the beginning ($i) and length ($len) of the array slice
# containing $stext you can "cut up" $data and concatenate the slices before
# and after $stext to the byte sequence you want to insert ($rtext):
#
# |<-- $stext -->|
# [...]['h','e','l','l','o'][...] <-- $data
# ^ ^ ^ ^
# | | | |
# | $i | $i+$len
# $i-1 $i+$offset (== $i+$len-1)
#
$rdata = $data[0..($i-1)] + [byte[]]$rtext + $data[($i+$len)..($data.Count-1)]
[IO.File]::WriteAllBytes($f, $rdata)
You'll need to adjust this code if you want the replacement to work differently (replace other occurrences as well, replace a different occurrence, …).
But it didn't work. It worked just like the first code. Help ?
This batch code is coming from this site and there is a link to discussion why it doesn't work with special characters.
Yes, the PowerShell replace command can replace the string and keep the special characters. To call it from within your batch script, use the following line
powershell -command "(get-content Ayylmao.txt) -replace 'hello','xello' | set-content Ayylmao.txt"
If you want to enter your parameters from the command line, then the line would be
powershell -command "(get-content %3) -replace '%1','%2' | set-content %4"
And if you want to use variables defined in the batch script, it is the same as you would for any batch script
set file=Ayylmao.txt
set Search_criteria=hello
set Replace_criteria=xello
powershell -command "(get-content %file%) -replace '%Search_criteria%','%Replace_criteria%' | set-content %file%"
My text file contains one line comments that all being with "// ". Two forward slashes and a space. These may either take up the whole line or just the last part of a line. Each comment does not extend beyond the line that it's on. So no /* */ type comments crossing multiple lines.
In simple terms, all comments start with "//space" anywhere on the line. Anything starting with "//space" should be removed and trailing spaces on that line should also be removed. Leading spaces should stay. Any blank lines should be removed.
Sample file:
// This is a comment
x = 1 // This is also a comment after the double slash
x = 2
x = 3 // The above is a blank line
// Comment on this record but nothing precedes it, so should be deleted.
y = 4 // A line with leading spaces that should be kept.
z = "//path"; // The first double slashes are not a comment since the space is missing after the "//"
// Last comment line.
Result file (no trailing spaces, but keep leading spaces.:
x = 1
x = 2
x = 3
y = 4
z = "//path";
I can remove the blank lines using gc file.txt | Where-Object { $_ -ne ''} > result.txt. However I'm having trouble with reading just the beginning part of a line up to the "//" comment part.
I also tried findstr but haven't found how to read each line up to the "//" and then trim spaces out.
I could write a script program to loop throught the file and do this, but it seems like there should be a way to accomplish it using a simple one or two line powershell or bat file command.
What is the easiest way (shortest amount of code) to remove these comments while keeping the uncommented contents of the file?
Since you seem to equate "easy" with "short", here's a fairly simple solution:
gc .\samplefile.txt|%{$_-replace"(.*)(// .*)",'$1'}|?{$_}
if it's really that important to you :-)
A bit more verbose version (still using regex):
Get-Content .\samplefile.txt | Where-Object {
-not ([String]::IsNullOrEmpty($_.Trim()) -or $_-match"^\s*// ")
} |ForEach-Object { $_ -replace "(.*)(// .*)",'$1' }
That being said, I would (personally) go for a more verbose and easier-to-read/maintain solution:
To remove everything after //, the easiest way is to find the first occurrence of // with String.IndexOf() and then grab the first part with String.Substring():
PS C:\> $CommentedString = "Content // this is a comment"
PS C:\> $CommentIndex = $CommentedString.IndexOf('// ')
PS C:\> $CommentedString.Substring(0,$CommentIndex)
Content
For the indented comments you can also use String.Trim() to remove whitespace from the beginning and end of the string:
PS C:\> " // Indented comment" -match '^//'
True
You can use the ForEach-Object cmdlet to go through every line and apply the above:
function Remove-Comments {
param(
[string]$Path,
[string]$OutFile
)
# Read file, remove comments and blank lines
$CleanLines = Get-Content $Path |ForEach-Object {
$Line = $_
# Trim() removes whitespace from both ends of string
$TrimmedLine = $Line.Trim()
# Check if what's left is either nothing or a comment
if([string]::IsNullOrEmpty($TrimmedLine) -or $TrimmedLine -match "^// ") {
# if so, return nothing (inside foreach-object "return" acts like "coninue")
return
}
# See if non-empty line contains comment
$CommentIndex = $Line.IndexOf("// ")
if($CommentIndex -ge 0) {
# if so, remove the comment
$Line = $Line.Substring(0,$CommentIndex)
}
# return $Line to $CleanLines
return $Line
}
if($OutFile -and (Test-Path $OutFile)){
[System.IO.File]::WriteAllLines($OutFile, $CleanLines)
} else {
# No OutFile was specified, write lines to pipeline
Write-Output $CleanLines
}
}
Applied to your sample:
PS C:\> Remove-Comments D:\samplefile.txt
x = 1
x = 2
x = 3
Like a great many text processing problems, there is a simple solution using JREPL.BAT - a powerful regex text processing utility for the Windows command line. It is pure script (hybrid JScript/batch) that runs natively on any Windows machine from XP onward. Full documentation is embedded within the script.
jrepl "^(.*?)\s*// " "$1!=''?$1:false" /jmatch /f test.txt /o out.txt
You can overwrite the original file by specifying - as the output file:
jrepl "^(.*?)\s*// " "$1!=''?$1:false" /jmatch /f test.txt /o -
I've tested, and it gives the exact output you are looking for.
If you put the command within a batch script, then you must use call jrepl
Tha Batch file below do what you want. Sorry, but there is not an "easy short code" way to do this...
#echo off
setlocal EnableDelayedExpansion
rem Set the maximum number of trailing spaces as a power_of_2-1 value. For example, for 15 spaces:
set spcPow2=4
set "spaces= "
for /L %%i in (1,1,%spcPow2%) do set "spaces=!spaces!!spaces!"
set /A spcPow2-=1
rem Process all lines, excepting empty ones and lines that start with "/"
setlocal DisableDelayedExpansion
for /F "eol=/ delims=" %%a in (test.txt) do (
set "line=%%a"
rem Split line at "// " and get the first part
setlocal EnableDelayedExpansion
for /F "delims=¡" %%b in ("!line:// =¡!") do (
endlocal
set "line=%%b"
)
rem Eliminate trailing spaces
setlocal EnableDelayedExpansion
set spc=0
for /L %%b in (%spcPow2%,-1,0) do (
set /A "newSpc=spc+(1<<%%b)"
for %%n in (!newSpc!) do if "!line:~-%%n!" equ "!spaces:~-%%n!" set "spc=%%n"
)
if !spc! gtr 0 for %%n in (!spc!) do set "line=!line:~0,-%%n!"
rem Show resulting line
if defined line echo !line!
endlocal
)
EDIT: New solution added
#set #x=1 // & CScript //nologo //E:JScript "%~F0" < samplefile.txt & goto :EOF
WScript.Stdout.Write(WScript.Stdin.ReadAll().replace(/(.*)\/\/ .*/g,"$1"))
Copy previous code into a file with .BAT extension, that is, it is a Batch file!
I admit I am a noob when it comes to CMD and bat scripting hence maybe my question has already been answered but I couldn't find it because I am unfamiliar with the terminology.
Basically I am currently running CMD to create a txt file for a directory content, that works fine but I would like to improve this process and started to look into a batch file to run this for multiple directories and by date but only get confused with the commands.
I would really appreciated if you maybe show me the right direction to look up the possible commands. Here is was I am basically trying to achieve:
Scan Directory 1, create log file with all content (filename) with modification of date DDMMYYYY and save under Directory 1 (existing on Desktop)
Repeat above for Directory 2, 3, 4 etc.
Now I am not sure how to approach this and where to start. It looks so simply yet I am have not managed to get to work.
assuming you are on Vista or better and your date format is dd/mm/yyyy:
for /d %%a in ("%userprofile%\Desktop\Directory*") do (
for %%b in ("%%~fa\*") do (
set "fname=%%~fb"
for /f %%c in ("%%~tb") do set "fdate=%%c"
setlocal enabledelayedexpansion
echo !fname! !fdate:/=! >> "%%~fa\LOGFILE.TXT"
endlocal
)
)
First of all your batch script to parse current date-time will be locale specific. As long as you do not plan to use it on non-US Windows it will be fine. My solution was to use simple VBS script to generate current timestamps
So code of my batch file looks like
#echo off
call GetToday.bat
call %TEMP%\SetToday.bat
SET LOGFILE=Log.%TODAY%.log
echo %LOGFILE%
Use your log here
GetToday.bat:
#echo off
set TOOLS_HOME=%~dp0
cscript /NoLogo %TOOLS_HOME%\Today.vbs >%TEMP%\SetToday.bat
call %TEMP%\SetToday.bat
Today.vbs:
Dim d
d = Now
WScript.Echo "SET TODAY=" & Get2Digit(Year(d)) & Get2Digit(Month(d)) & Get2Digit(Day(d))
Function Get2Digit(value)
if 10 > value Then
Get2Digit = "0" & value
Else
Get2Digit = Right(value, 2)
End If
End Function
However given Today.vbs generates today date in form YYMMDD. From my experience such suffixes are much more useful, you could just sort you files by name to find specific date
In PowerShell something like this should work:
$folders = 'C:\path\to\Directory1', 'C:\path\to\Directory2', ...
$refDate = (Get-Date '2013-05-27').Date
$recurse = $false
foreach ($d in $folders) {
$output = Join-Path $d 'filelist.txt'
Get-ChildItem $d -Recurse:$recurse | ? {
-not $_.PSIsContainer -and $_.LastWriteTime.Date -eq $refDate
} | % { $_.Name } | Out-File $output
}
If you want to recurse into the subfolders of the scanned folders you need to change $recurse to $true and perhaps change $_.Name to $_.FullName, so you get the filename with the full path instead of just the filename.