My batch script in DOS is processing way to slow, so someone recomended I use powershell. I'm running it now for my first time on windows, but I've never used it before today. I hear it's similar to batch scripting, so I'm currently converting my batch script into a powershell script. Below is my script so far half way through conversion:
# ask user for network share and file that they would like to search
$textFilePath = Read-Host Please enter filesystem location of "filenames.txt". Include drive letter or // at start of path
$uncPath = Read-Host Please enter the UNC path you would like to search. Include // at start of path.
# REM check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
IF (Test-Path %uncPath%) {
echo Network Path Exists. Searching %uncPath% for files with same name and extension as filenames in the filenames.txt file
for (/r %uncPath% %%G IN (*)) {for (/F "tokens=*" %%i in (%textFilePath%)) {if (%%~nxG==%%i) {echo %%~nxG,%%~fG >> filenamesOutput.txt}}}
pause
}
IF (!(Test-Path exist %uncPath%)) {
echo File not found
GOTO:userInput
}
I'm currently learning the powershell commands as I go and changing the batch command to powershell. Help with conversion would be appreciated.
AFter Edit:
Here's my original batch script:
#echo off
echo Please enter filesystem location of "filenames.txt". (Include Drive letter or // at start of path)
set /p textFilePath=Enter The Value:%=%
:userInput
REM ask user for network share and file that they would like to search
echo Please enter the UNC path you would like to search. (Include // at start of path)
set /p uncPath=Enter The Value:%=%
REM check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
IF exist %uncPath% (
echo Network Path Exists. Searching %uncPath% for files with same name and extension as filenames in the filenames.txt file
for /r %uncPath% %%G IN (*) DO for /F "tokens=*" %%i in (%textFilePath%) DO if %%~nxG==%%i echo %%~nxG,%%~fG >> filenamesOutput.txt
pause
)
IF NOT exist %uncPath% (
echo File not found
GOTO:userInput
)
After 2nd Edit:
$VerbosePreference = "continue"
# ask user for network share and file that they would like to search
$textFilePath = Read-Host Please enter filesystem location of "filenames.txt". Include drive letter or // at start of path
$uncPath = Read-Host Please enter the UNC path you would like to search. Include // at start of path.
# check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
IF (Test-Path $uncPath){
echo "Network Path Exists. Searching $uncPath for files with same name and extension as filenames in the filenames.txt file"
foreach($file in Get-ChildItem $uncPath -Recurse) {
# Get-Content reads in a file, line by line
foreach($line in Get-Content $_.FullName) {
# if goes in here
if($file.Name -eq $line){
echo $file.Name
"{0},{1}" -f $file.Name,$file.FullName | Out-File filenamesOutput2.txt -Append
}
}
}
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
}
IF (!(Test-Path $uncPath)){
echo "UNC path not found"
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
}
Variables:
In PowerShell, variable references are always prefixed with $ (much like in PHP or Perl).
So any variable you would assign and dereference in cmd/batch as:
set /p varname= somevalue
echo %varname%
Would in PowerShell be treated as (notice no difference between assigning and dereferencing):
$varname = "varvalue"
Write-Host $varname
So your exists/Test-Path statements should be:
if(Test-Path $uncPath){
# Loops in here
# "#" starts is a single-line comment btw
}
For loop:
In cmd, the for loop construct behaves different depending on the first switch:
for /r roughly means "loop recursively through filesystem tree"
for /f roughly means "loop through tokens in a file"
it should be noted that cmd for loops use parameters, denoted by the prefix %% (like %%G or %%i in your example)
PowerShell doesn't have this concept and just uses variables in loops. Thus, your for /r and for /f loops become:
# Get-ChildItem is equivalent to the "dir" command
# The -Recurse is pretty self-explanatory ( = /S)
foreach($file in Get-ChildItem $uncPath -Recurse) {
# Get-Content reads in a file, line by line
foreach($line in Get-Content $textFilePath) {
# if goes in here
}
}
Parameter modifiers:
In cmd, a parameter (like %%G) can be modified using a tilde (~) followed by a sequence of modifier characters.
%%~nG means "treat %%G as a path, return the name without extension"
%%~xG means "treat %%G as a path, return the file extension"
so %%~nxG naturally means "return filename WITH extension".
In PowerShell, everything is a .NET object, and in the case of $file, it's a FileInfo object. From the FileInfo object, the filename (WITH the extension) is stored in the Name property, so your if statement:
if %%~nxG==%%i
becomes:
if($file.Name -eq $line){
# echo and output goes in here
}
%%~fG means "treat %%G as a path, give me the full rooted path"
Again, the fact that $file is a FileInfo object comes in handy, the full path can be accessed from the FullName property:
"{0},{1}" -f $file.Name,$file.FullName | Out-File filenamesOutput.txt -Append
The -f operator is a simplified syntactic shortcut to String.Format, .NET's version of sprintf if you will.
Ultimately resulting in something like:
# ask user for network share and file that they would like to search
$textFilePath = Read-Host 'Please enter filesystem location of "filenames.txt". Include drive letter or \\ at start of path'
$uncPath = Read-Host 'Please enter the UNC path you would like to search. Include \\ at start of path.'
# check if network path is available. If it is, search network directory for files with same name as the strings in filenames.txt
if (Test-Path $uncPath) {
Write-Host "Network Path Exists. Searching $uncPath for files with same name and extension as filenames in the filenames.txt file"
foreach($file in Get-ChildItem $uncPath) {
foreach($line in Get-Content $textFilePath) {
if($file.Name -eq $line){
'"{0}","{1}"' -f $file.Name,$file.FullName | Out-File filenamesOutput.txt -Append
}
}
}
pause
} else {
Write-Host "File not found"
}
Related
I have around 5000 folders each containing a dos executable and required files.
Currently I am using a for loop to call the below code. it takes a long time to execute one by one as each execution takes around 5 seconds.
Is there an option where I can execute all the exe files at the same time ?
Any ideas?
Thanks
I tried using
start "" 1/ddd.exe input.dat
start "" 2/ddd.exe input.dat
start "" 3/ddd.exe input.dat
.
.
.
in a batch file. input.dat has the arguments to pass on to the exe. but the exe opens up a new window and its not taking the arguments. first argument is "2" run to certain part of the exe and second any number to exit the program after it has finished.
You tried
for /D %%a in (*) do (
echo processing: %%a
start /B "Name" cmd.exe "cd %%a & ddy.exe < parameters.txt"
)
I would prefer start /D "%%a" /min "Name" cmd.exe /c "ddy.exe < parameters.txt". /B causes them to use the same console and they may block each other. /D sets the working folder (no need for cd), /min minimizes the windows to keep your screen clean.
And don't forget /c with the cmd command (without, you get no parallel processes).
As a whole:
for /D %%a in (*) do (
echo processing: %%a
start /D "%%a" /min "Name" cmd.exe /c "ddy.exe < parameters.txt"
)
This is a -very- minimalistic script to run N commands at a time from a list. If you are on a supported Windows system, it will have PowerShell.
There is no error checking or proper help information. It writes stdout to the specified log file, but does nothing with the exit code from the command. If something fails, it would need to be identified from the log file.
To use this, put the following code into the file Invoke-JobList.ps1
Create a .csv file with the commands you want to run and a different log file name for each command. The log file name cannot be the same for multiple commands. If you have 5000 commands to process, you will probably need to write a script/program to produce it.
I provided a sample .csv file and a batch file that I used for testing. You do not need to use to.bat.
=== Get-Content .\Invoke-JobList.ps1
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string[]]$jobFile
,[Parameter(Mandatory=$false)]
[int]$nConcurrent = 2
)
$jobs = Import-Csv -Path $jobFile
$jobHash = #{}
$nJobsRunning = 0
foreach ($job in $jobs) {
if ($nJobsRunning -lt $nConcurrent) {
Write-Verbose -Message "starting command $($job.command)"
$j = Start-Job -ScriptBlock ([ScriptBlock]::Create($job.command))
$jobHash[$j] = $job.logfile
$nJobsRunning++
}
while ($nJobsRunning -ge $nConcurrent) {
# wait for one or more jobs to state Completed
$jobsRunning = Get-Job
foreach ($jobRun in $jobsRunning) {
if (($null -ne $jobHash[$jobRun]) -and ($jobRun.State -eq 'Completed')) {
Receive-Job -Job $jobRun | Out-File -FilePath $jobHash[$jobRun]
Remove-Job -Job $jobRun
$jobHash.Remove($jobRun)
$nJobsRunning--
}
}
}
}
Write-Verbose -Message $($nJobsRunning.ToString() + " remaining jobs")
# Wait for all remaining jobs to complete
while ($nJobsRunning -gt 0) {
$jobsRunning = Get-Job
foreach ($jobRun in $jobsRunning) {
if (($null -ne $jobHash[$jobRun]) -and ($jobRun.State -eq 'Completed')) {
Receive-Job -Job $jobRun | Out-File -FilePath $jobHash[$jobRun]
Remove-Job -Job $jobRun
$jobHash.Remove($jobRun)
$nJobsRunning--
}
}
}
=== Get-Content .\joblist3.csv
command,logfile
C:\src\jobs\to.bat 10,ss-001.txt
C:\src\jobs\to.bat 10,ss-002.txt
C:\src\jobs\to.bat 10,ss-003.txt
C:\src\jobs\to.bat 10,ss-004.txt
C:\src\jobs\to.bat 10,ss-005.txt
C:\src\jobs\to.bat 10,ss-006.txt
C:\src\jobs\to.bat 10,ss-007.txt
=== Get-Content .\to.bat
#ECHO OFF
SET "TO=%1"
IF "%TO%" == "" (SET "TO=5")
REM Cannot use TIMEOUT command
ping -n %TO% localhost
EXIT /B 0
Invoke it with parameters.
.\Invoke-JobList.ps1 -jobFile joblist3.csv -nConcurrent 3 -Verbose
I am trying to get a list of all files within a directory including those within all subfolders.
The columns I would like are the Filename, Path, Size and Date.
I have tried to do my own research and come close but not yet hit the full solution.
I can get the filepath and filename together with date and size using this command below, unfortunately I cannot get all the files within a subfolders.
dir /t > filelist1.txt
This below CMD command does get the filenames from all subfolders but I cannot get it to produce dates.
(#For /F "Delims=" %A in ('dir /B/S/A-D') Do #Echo %~fA %~zA) >filelist.txt
I thought maybe do this to include dates but it didn't work.
(#For /F "Delims=" %A in ('dir /B/S/A/D') Do #Echo %~fA %~zA) >filelist.txt
This file also gives me the path and filename together which I can accept (I will use Excel to separate) but is it possible to have the path and filename separated?
Also it is possible to have those columns separated by tab for easier Excel import?
This could be done with %~ variables in a cmd.exe batch script. But, it is easier and more readable in PowerShell. The output is in a file named FileList.csv.
Get-ChildItem -Recurse |
ForEach-Object {
[PSCustomObject]#{
FileName = $_.Name
Path = $_.Directory
Size = $_.Length
Date = $_.LastWriteTime
}
} |
Export-Csv -Path './FileList.csv' -Delimiter "`t" -Encoding ASCII -NoTypeInformation
If you do not want to run the .ps1 file from the .bat script, it can, with enough effort, be put into the .bat file script.
powershell -NoLogo -NoProfile -Command ^
"Get-ChildItem |" ^
"ForEach-Object {" ^
"[PSCustomObject]#{" ^
"FileName = $_.Name;" ^
"Path = $_.Directory;" ^
"Size = $_.Length;" ^
"Date = $_.LastWriteTime" ^
"}" ^
"} |" ^
"Export-Csv -Path './FileList.csv' -Delimiter "`t" -Encoding ASCII -NoTypeInformation"
I have a directory of PDFs that I need to rename according to a repeating sequence. In English, the command would go like this:
For each file in the directory, and in the order that the files are listed, alternate names between "Type 1", "Type 2" , "Type 3" until the each item has been renamed.
I'm hoping to do this in Powershell or on the command line.
The ForEach-Object cmdlet accepts a script block for the -Begin argument which can be used to initialize a counter variable. This variable can then be incremented on each iteration.
dir <directory_of_pdfs> | ForEach-Object -Begin { $i=1 } -Process { Rename-Item $_ "Type $($i++)" }
Some time ago, #Magoo was nice enough to help me in working out a FOR /F command to archive files into 7-zip:
Using FOR or FORFILES batch command to individually archive specific files
Since then I've expanded it somewhat and want to do more elaborate things with it. However, to keep this question simple, I've included the basics to try and get this working, which I haven't had much success at.
I'm rather new to PowerShell and I have some specific reasons to use this instead of batch files, moving forward.
I understand that some more experienced users may note that I will have a reduction in performance by using such statements in PowerShell, but it isn't an important issue for me.
$env:Path += ";C:\Program Files\7-Zip"
$sourcedir = read-host "Enter the directory to archive: "
foreach ($aname in {
'cmd /c dir /s/b /a-d "$sourcedir\*.iso" '
'cmd /c dir /s/b /a-d "$sourcedir\*.daa" '
'cmd /c dir /s/b /a-d "$sourcedir\*.nrg" '
'cmd /c dir /s/b /a-d "$sourcedir\*.flp" '}
) {
IF NOT EXIST $aname.7z (
echo 7z a -t7z "$aname.7z" "$aname" -mx9 -mmt >> Z:\test\7z-log.txt
ECHO "$aname" archived.
) ELSE (
ECHO "$aname" archive file already exists.
)
}
I got into some trouble with the IF EXIST statement and even when I removed the IF and had a one-line ECHO just to simplify it even more, but I couldn't get it to output what I wanted.
So, I tried a different approach:
$env:Path += ";C:\Program Files\7-Zip"
$sourcedir = read-host "Enter the directory to archive: "
$dir_iso = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.iso" }
$dir_daa = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.daa" }
$dir_nrg = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.nrg" }
$dir_flp = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.flp" }
foreach ($aname in $dir_iso,$dir_daa,$dir_nrg,$dir_flp) {
ECHO "$aname" archived.
}
But what this did, is clumped each item of each type together, then appended "archived" to that set. Something like:
C:\folder1\iso1.iso C:\folder1\iso2.iso C:\folder1\iso3.iso archived.
C:\folder2\image.nrg archived.
C:\folder3\app1.flp C:\folder3\app2.flp archived.
instead of:
C:\folder1\iso1.iso archived.
C:\folder1\iso2.iso archived.
C:\folder1\iso3.iso archived.
C:\folder2\image.nrg archived.
C:\folder3\app1.flp archived.
C:\folder3\app2.flp archived.
I'm having a real hard time with getting this to work. Can anyone help?
Thanks.
The first thing I see here is you are using this to get file information from the filesystem
$dir_iso = ForEach-Object { cmd /c dir /s/b /a-d "$sourcedir\*.iso" }
More specifically cmd /c dir /s/b /a-d "$sourcedir\*.iso". This would translate easily to Get-ChildItem
$dir_iso = Get-ChildItem -Path $sourcedir -Filter "*.iso" -Recurse -File | Select-Object -ExpandProperty FullName
Path is the file path of the folder you are checking
Filter you want only files ending with .iso
Recurse all subdirectories are checked
File returns only files and not directories (PowerShell 3.0 or higher!. There is a simple equivalent if this is an issue. )
Select-Object -ExpandProperty FullName would just return the full path of the files found in an array.
IF EXIST could be replaced by Test-Path
If(Test-Path "$aname.7z"){
Do stuff...
}
As for the ForEach loop foreach ($aname in $dir_iso,$dir_daa,$dir_nrg,$dir_flp) there are a couple good approaches with this but the simplest transistion would be
$dir_iso + $dir_daa + $dir_nrg + $dir_flp | ForEach-Object{
Do Stuff
}
I would probably build the file collection in one variable to begin with to avoid the to concat the arrays together
$files = Get-ChildItem -Path $sourcedir -Recurse -File | Where-Object{$_.Extension -match "(iso|daa|nrg|flp)$" } | Select-Object -ExpandProperty FullName
$files | ForEach-Object{
Write-Host "$_"
}
Use write-host instead of echo.
Multidimensional array, so you'll need an inner loop.
foreach ($aname in $dir_iso,$dir_daa,$dir_nrg,$dir_flp) {
foreach ($bname in $aname) {
write-host "$bname" archived.
}
}
Another possibility; this pipes the commands together rather than storing each in a separate variable.
Get-Item $sourcedir\* |
Where {$_.Extension -like ".iso" -or $_.Extension -like ".daa" -or $_.Extension -like ".nrg" -or $_.Extension -like ".flp"} |
Foreach-Object {
if (-Not (Test-Path "$_.BaseName.7z"))
{
Write-Host $_.FullName not yet archived.
}
else
{
Write-Host $_.FullName already archived.
}
}
I'm trying to write a powershell script that will create outlook logs (using NETMON) from the local client to the exchange server. The problem is the file name is not incrementing correctly. I found this incrementing script online and i understand the logic (kinda). Am i using the right way to initialize the counter? It isn't renaming the file correctly. It gives the 2nd file just a _ then the 3rd file gets a _2 then after that it stops renaming files.
ALSO is invoke-expression waiting for my cmd to exit or will it just go to the next step of $count-- ?
#Directory to complete script in
$path = "c:\Outlook_Logs\"
cd $path
$cmdline = "Nmcap.exe /network * /captureipv4.address==X.X.X.X /file :\outlook_logs\client_$count.chn:100MB"
#Writes out number of files in directory to console
$count = (get-childitem $path -name).count
Write-Host "Number of Files: $count"
#Sorts items by decsending order
$items = Get-ChildItem | Sort Extension -desc
#Deletes oldest file by file extension number
del $items[0]
#Copy file from original directory to backup directory
Copy-Item c:\Outlook_Logs\* c:\Outlook_Logs_Temp\
#Sorts items by decsending order
$items = Get-ChildItem | Sort Extension -desc
#Renames files in quotes after NewName argument
$items | ForEach-Object -begin { $count= (get-childitem $path -name).count } -process { rename- item $_ -NewName "client_$count.cap";Invoke-Expression "$cmdline"; $count-- }
There's no need to increment the names yourself. chn captures files are created and names incrementally automatically when they reach the size specified after the colon.
What happens when you lower the size to 1MB and execute the command?