I have an interactive PowerShell using windows forms, meaning a PowerShell script displays some controls on a form asking for input and finally runs robocopy, when the user clicks the button.
This is a simple script displaying the problem.
You can copy the code, save it to a Power Sell script and run. It needs a:\tmp\tmp folder to successfully run, see the CopyFolder function.
function createdMainForm {
$mainForm = New-Object System.Windows.Forms.Form
$btn = New-Object System.Windows.Forms.Button
$btn.Text = "RoboCopy"
#register click event
$btn.add_click({Add_click})
$mainForm.Controls.Add($btn)
$mainForm.ShowDialog()
}
#click event
function Add_click() {
CopyFolder
}
function CopyFolder() {
$sourseFolder = "C:\tmp\tmp"
$targetFolder = "C:\tmp\tmp2"
$Logfile = "c:\tmp\a.log"
robocopy $sourseFolder $targetFolder /tee /log:$Logfile
}
function ReadMode() {
Write-Host Mode [1 for GUI mode]
$mode = Read-Host
if ($mode -eq 1) {
createdMainForm
} else {
CopyFolder
}
}
ReadMode
I want to capture the robocopy progress in a log file as well as in the console.
However only the log file capture the output, while the console "hangs" until end.
I found that is works smoothly when PowerShell does not display form, but simply runs the command.
The script works in two modes. enter 1 for "form mode" anything else for console mode, where the log actually written to console and file.
How can I use the form, and display the progress in the console?
Use the proper robocopy parameters:
/log:<LogFile> Writes the status output to the log file (overwrites the existing log file).
…
/tee Writes the status output to the console window, as well as to the log file.
robocopy $s $t /tee /log:C:\path\to\your.log
As for updating the output of your embedded control, you probably need to refresh your form on a timer:
$timer = New-Object Windows.Forms.Timer
$timer.Interval = 1000
$timer.add_tick({$form.Refresh()})
Start the timer when calling robocopy and stop it after the command completes. Details will vary depending on your actual code (which you chose to omit).
Edit: I think I finally understood what you're trying to achieve and what problem you have getting there.
You're running robocopy from a scriptblock that is associated with a form element. Because of that STDOUT output doesn't go to the console. You need to take the robocopy output (the success output stream) and write it to the host console yourself:
function CopyFolder() {
...
robocopy $sourseFolder $targetFolder /tee /log:$Logfile | Write-Host
}
Related
Below script list, the files, change the permissions on the file and moving it to a different folder. Sometimes the script is moving the file before the contents fully generated.
Need to add a delay after getting the list before starting the loop. Is this possible? Please help how to achieve this scenario to implement.
Can we use the sleep command to achieve this?
Script to change the file permissions and move it to main folder
function starts here
function mv_purge_files
{
cd $SRC
if [ "$?" = "0" ]; then
for c in $(/usr/bin/ls *)
do
echo "ext: changing file permission $c"
/usr/bin/chmod 775 $c
echo "ext: moving $c"
/usr/bin/mv $c $TGT/$c
done
else
echo "Error accessing folder " $SRC
fi
}
program starts here
SRC=/temp/file.in
TGT=/tgt/purge
mv_purge_files
Is it possible to make a Batch, or .vbs if needed, is it possible to make batch have a little icon down there with the flag, battery and volume.
I want it to be a "Shutting down in xx minutes/hours", and mayyybbe clicking it cancels' it.
Thanks in advance :)
Well, I know how to accomplish part of what you want -- making a systray balloon tip by borrowing from PowerShell. But I don't know how to make it listen for dismissal of the balloon. Maybe someone else can offer another answer building upon mine?
Anyway, I use this for a conversion script I made to convert flac to mp3 in batches. Feel free to hack it for your own evil purposes.
#echo off
setlocal
for %%I in (*.flac) do (
rem // This initiates the systray balloon.
call :systray converting from "%%~nxI" to "%%~nI.mp3"
)
goto :EOF
rem // Here's the :systray function
:systray <message>
setlocal enabledelayedexpansion
set "args=%*"
set "args=!args:'=''!"
set "code="[void] [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');^
$o=New-Object Windows.Forms.NotifyIcon;$o.Icon='%systemroot%\System32\PerfCenterCpl.ico';^
$o.BalloonTipIcon='Info';$o.BalloonTipText='!args!';$o.BalloonTipTitle='%~nx0';$o.Visible=1;^
$o.ShowBalloonTip(10000);Start-Sleep -M 12000;$o.Dispose();Exit""
start /b "" "powershell" %code%
endlocal & goto :EOF
The n values in $o.ShowBalloonTip(n1) and Start-Sleep -Mn2 are in milliseconds. Salt to taste.
Update: I found a bit about registering an event for $o.BalloonTipClicked as well as a lovely example in the wild. Basically, replace this:
$o.ShowBalloonTip(10000);Start-Sleep -M 12000;$o.Dispose();Exit
... with this:
$o.ShowBalloonTip(10000);register-objectevent $o BalloonTipClicked clicked;^
if (wait-event clicked -t 12) {$true} else {$false}; $o.Dispose(); Exit
You also need to execute powershell in a single threaded apartment for the event to work.
start /b "" "powershell" -STA %code%
Now, you need to figure out how to make that relevant back in the context of your batch process. For one thing, you'd probably no longer be able to use start /b to make the balloon tip non-blocking, and you'd probably use a for /F loop to capture the output of the powershell command.
Adding to your worries, I propose that "Shutting down in xx minutes" is not entirely user-friendly. What if "Shutting down in 30 minutes" appeared 29 minutes ago, but the user just now saw it? "Shutting down at 9:51 AM" might be better.
So with all this in mind, since what you want is event driven and since the batch language doesn't handle date-time math all that easily, I suggest doing the whole damn thing in PowerShell. Save this with a .ps1 extension. Right-click and run with PowerShell. Or if you want to execute it from a cmd console, do powershell ".\scriptname.ps1".
set-executionpolicy remotesigned
if ([threading.thread]::CurrentThread.GetApartmentState() -eq "MTA") {
& powershell.exe -window minimized -sta $MyInvocation.MyCommand.Definition
exit
}
[void] [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$minutes = 30
$launch_time = (Get-Date).AddMinutes($minutes).ToShortTimeString()
$o = New-Object Windows.Forms.NotifyIcon
$o.Icon = "$env:SystemRoot\System32\PerfCenterCpl.ico"
$o.BalloonTipIcon = "Info"
$o.BalloonTipText = "Shutting down at $launch_time"
$o.BalloonTipTitle = "Shutdown pending..."
$o.Visible = 1
function show-balloon { $o.ShowBalloonTip($minutes * 60 * 1000) }
show-balloon
$o_hover = [Windows.Forms.MouseEventHandler]{ show-balloon }
$o.add_MouseMove($o_hover)
register-objectevent $o BalloonTipClicked clicked
if (wait-event clicked -t ($minutes * 60)) {
remove-event clicked
$o.BalloonTipText = "Have a nice day!"
$o.BalloonTipTitle = "Shutdown aborted"
$o.ShowBalloonTip(10000)
if (wait-event clicked -t 10) { remove-event clicked }
} else {
# Initiate shutdown sequence on my mark. Authorization rojo alpha 3. Mark.
stop-computer
}
unregister-event clicked
$o.Dispose()
Bill_Stewart, if you're reading this, I know you're pleased. As it happens, PowerShell is indeed the correct tool for the job this time.
So, I am fairly new to PowerShell and need to create a script to rename the computers in our office. That portion of the script works. The part I am having trouble with is the output.
I have this set in task scheduler, but when it runs I do not see if the rename was successful. Below is my script and below that is what goes into the text file.
start-transcript -path C:\Users\abhagwandin.SENECA\Desktop\RenameResults.txt
$CSV = Import-Csv "C:\Users\abhagwandin.SENECA\Desktop\Computer Desktop Names Test.csv" -Header OldName, NewName
Foreach ($name in $CSV)
{
write-output $name
netdom renamecomputer $name.OldName /newname: $name.NewName /userd: admin /passwordd: pass /usero: admin /passwordo: pass /reboot /force
}
stop-transcript
-------------------------------------------------------------------------------
**********************
Windows PowerShell Transcript Start
Start time: 20150520154216
Username :
Machine : (Microsoft Windows NT 6.1.7601 Service Pack 1)
**********************
Transcript started, output file is C:\Users\abhagwandin.SENECA\Desktop\RenameRe
sults.txt
OldName NewName
------- -------
JFLAHNYCD1 JFLAHERTY
**********************
Windows PowerShell Transcript End
End time: 20150520154218
**********************
You know that renaming a computer through a cmdline command in powershell instead of using the built in cmdlets can give problems with the output if you don't parse the output (and preferably create a new object for it)?
Why don't you use rename-computer or the rename() method of the win32_computersystem wmi class? Both can be used remotely so you don't even have to schedule tasks that way.
Just create an input file with the current name and the desired names and use a loop to process them.
Backstory: I need to programmatically find the differences between two files. I want to use WinMerge to generate a report (Tools -> Generate Report) that I can parse to get the differences between two files. I need this done using either a Groovy script or a bat script.
I was hoping that WinMerge would offer command line options to generate the report and then I could just use a Groovy Process object to execute WinMergeU.exe with the arguments. No such luck according to the command options I've found for WinMerge.
Next, I was hoping to be able to start WinMerge and send keystrokes to step through the menus to generate the report(Alt+T, R, Diff.html, [Enter]). I don't see a way to do that from a Groovy Process and I haven't found a way to do this in a bat script. I'm looking for something similar to WshShell.Sendkeys in VB. Is this a wild-goose chase?
UPDATE/Answer with PowerShell in a bat file:
I was intrigued by Knuckle-Dragger's comment about using a PowerShell script in a bat file.
$folder = "C:\DevTools\WinMerge\WinMergeU.exe"
ii $folder
Start-Sleep -m 1000
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
[Microsoft.VisualBasic.Interaction]::AppActivate("WinMerge")
Start-Sleep -m 100
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
Start-Sleep -m 100
[System.Windows.Forms.SendKeys]::SendWait("%F")
[System.Windows.Forms.SendKeys]::SendWait("o")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
[System.Windows.Forms.SendKeys]::SendWait("%T")
[System.Windows.Forms.SendKeys]::SendWait("r")
Start-Sleep -m 1000
[Microsoft.VisualBasic.Interaction]::AppActivate("Save As")
Start-Sleep -m 1000
[System.Windows.Forms.SendKeys]::SendWait("Diff.txt")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
To encapsulate this in a command window, save it to a file PowerShellScript.ps1:
start /b /wait powershell.exe -nologo -WindowStyle Hidden -sta *PowerShellScript.ps1*
Here is a powershell example to activate winmerge and send some keys.
EDIT: Reduced copy pasta with some .NET variables. $SK = Sendkeys $AA = AppActivate $LRA = Reflect .NET.
$startapp = "C:\DevTools\WinMerge\WinMergeU.exe"
ii $startapp
$SK = "[System.Windows.Forms.SendKeys]::SendWait"
$AA = "[Microsoft.VisualBasic.Interaction]::AppActivate"
$LRA = "[void][System.Reflection.Assembly]::LoadWithPartialName"
Start-Sleep 1
$LRA+'("Microsoft.VisualBasic")'
$AA+'("WinMerge")'
Start-Sleep -m 100
$LRA+'("System.Windows.Forms")'
Start-Sleep -m 100
$SK+'("%F")'
$SK+'("o")'
$SK+'("{ENTER}")'
$SK+'("%T")'
$SK+'("r")'
Start-Sleep 1
$AA+'("Save As")'
Start-Sleep 1
$SK+'("Diff.txt")'
$SK+'("{ENTER}")'
To encapsulate this in a command window, save it to a file PowerShellScript.ps1: Note, changed the command syntax a bit, should work if you use the & {.\dot\source\path}
start /b /wait powershell.exe -nologo -WindowStyle Hidden -sta -Command "& {.\PowerShellScript.ps1}"
This is something that I threw together without testing, and I don't have WinMerge so testing isn't really an option.
RunMe.bat
start "" "C:\Path\WinMerge.exe" "file1" "file2"
GenerateDiffFile.vbs
GenerateDiffFile.vbs
Set s = CreateObject("WScript.Shell")
wscript.sleep(1000) ' Sleep for 1 second to allow time for WinMerge to finish loading and merge the files
s.SendKeys("%tr")
wscript.sleep(250) ' Give time for dialog box to appear
s.SendKeys("Diff.html{Enter}")
This is completely untested, but I think it is very close to what you need... You can see that the batch-file runs WinMerge, passing the two files to merge on the command line. Then it launches the VBS script, which pauses long enough to allow the launched WinMerge to be able to accept keyboard input. I used this page from Microsoft to know what keys to send.
I've been sending keys to programs for years. It works very reliably, as long as the program you want to send keys to keeps focus.
All I do is this:
echo y | "Batchfile needing the keystroke"
I am in the middle of moving to the cloud, migrating from SBS 2003 Active Directory, to 2008 R2.
I configured a new user, and noticed that the user was unable to reset their password.
My Server Admin showed me how to use net user.
I noticed that I can obtain information from some accounts and not others. With over 100 accounts to process, I thought I'd try PowerShell.
In this post (Use powershell to look up 'net user' on other domains?) Lorenzo recommends using Get-ADUser (and this applies to polling from another domain). When I run Get-ADUser from my PowerShell prompt, I receive a message stating that the commandlet is not recognized.
I am reading the user IDs from a text file, and sending the output to a log file so that I can send to the server admin for further analysis.
Here is my code so far (please note that I am completely new to PowerShell):
# Get our list of user names from the local staff.txt file
$users = get-content 'C:\Scripts\staff.txt'
# Create log file of output:
$LogTime = Get-Date -Format 'MM-dd-yyyy_hh-mm-ss'
$CompPath = "C:\Scripts\"
$CompLog = $CompPath + "NetUserInfo" + $LogTime + ".txt"
New-Item -path $CompLog -type File
foreach ($user in $users) {
#Testing user:
"Testing user: $user" | out-file $CompLog -Append
# Obtain user information using net user:
net user $user /domain >> $CompLog
# Pause to let system gather information:
Start-Sleep -Second 15
}
As the script runs currently, my log file will have two or three user names followed by the response "The request will be processed at a domain controller for domain (domain)"
If net user, from CMD, would return "System error 5 has occurred, Access is denied." This is not logged in the output file. IF net user, from CMD, would return user information, this is logged to the output file. I am currently receiving output for only a couple users, but when I run the command from CMD, I am able to retrieve information for at least ten.
My first thought was that I needed to wait for the net user command to complete (hence the Start-Sleep command) but that has not had any effect on the output.
Any assistance would be greatly appreciated.
Sorry, I don't have enough reputation to add a comment.
When you run programs in Powershell (such as net user... or ping) it should be running exactly the same as it would in the normal command prompt (cmd).
If I understand correctly you're getting different results when you (effectively) run the same thing in Powershell or Command Prompt. Is that right?
Are you using the ISE to build your script? If so you can set Breakpoints that will pause the script and allow you to see what the variables are. It could be that the $user variable isn't holding what you think it should be, or that the value doesn't match the name for a domain user account.
EDIT:
What happens when you run the net user ... command interactively (e.g. not as a script, but manually) in Powershell? Do you get an error? If so, what does the error say?
Additional related stuff:
You shouldn't need the Start-Sleep as the commands are run in order, and the next line shouldn't execute until the previous on has completed.
Also, which version of Powershell are you using?
(You can check by running $host.version.Major)
The Get-ADUser cmdlet requires version 3 (I believe) and also needs to import the Active Directory module.
The reason the error output is not being appended to the log file is because you are only redirecting the STDOUT (standard output stream). To also redirect the STDERR (standard error stream) change
net user $user /domain >> $CompLog
to
net user $user /domain 2>&1 $CompLog
This explains it a bit more:
http://www.techotopia.com/index.php/Windows_PowerShell_1.0_Pipes_and_Redirection#Windows_PowerShell_Redirection_Operators