When my script starts it moves a file from one directory to another. After the file is completely downloaded I launch an application.
This all works, but what I would like is a popup window to appear while the file is being moved (Large files).
When I debug my code once it hits the Move-Item Cmdlet it waits until that command is completed before it moves on. What I want to do is while the Move-Item Cmdlet is running, popup an information window.
I know how to do the popup and the Move-Item, I just don't know how to get it to work the way I want. Any ideas?
Popup code
#pop up window letting mechanic know we are waiting for the files to be downloaded before opeing the SMT application
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("The EAFR file is still being moved to the correct directory, please wait.",0,"SMT Status",0)
#Move-Item
$MLMoveDir = "C:\move\data\AutoUpload\"
Move-Item -LiteralPath ($filePath) $MLMoveDir
One option is to use WinForms to display a Please Wait dialog, rather than a Popup that has to be dismissed by the user. Something like:
Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object system.Windows.Forms.Form
$Label = New-Object System.Windows.Forms.Label
$Form.Controls.Add($Label)
$Label.Text = "Copying file, please wait."
$Label.AutoSize = $True
$Form.Visible = $True
$Form.Update()
#Move-Item
$MLMoveDir = "C:\move\data\AutoUpload\"
Move-Item -LiteralPath ($filePath) $MLMoveDir
#Hide popup
$Form.Close()
So what you could do is start the Move-Item as a job, and then do a While((get-job "jobname").state -ne completed){do popup}. I would look something like this:
#Move-Item
$MLMoveDir = "C:\move\data\AutoUpload\"
$MoveJob = Start-Job -scriptblock {Move-Item -LiteralPath ($filePath) $MLMoveDir}
#Show Popup
While($movejob.state -ne "Completed"){
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("The EAFR file is still being moved to the correct directory, please wait.",1,"SMT Status",0)
}
That way the popup shows for 1 second, and if the move is still happening it shows it again. I don't know that it would even appear to disappear/re-show so it would likely be seamless.
Related
I am looking for a way to start a process in a new console window or the same window and catch its output, I can open process in new window using:
[Diagnostics.Process]::Start("C:\test.exe","-verbose -page")
This will open new window witch I can interact with but I cannot redirect output for it (output I mean whole interaction with window like key press and messages)
So I thought I can try with:
Start-Transcript -path "C:\test.txt" -append
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = "C:\test.exe"
$ps.StartInfo.Arguments = " -verbose -page"
$ps.StartInfo.RedirectStandardOutput = $True
$ps.StartInfo.UseShellExecute = $false
$ps.start()
while ( ! $ps.HasExited ) {
write-host = $ps.StandardOutput.ReadToEnd();
}
Now here I get get output but without interaction, so I need some sort of option or procedure to start this process in same or different console and catch my interaction with it to file.
It is important as application sometimes asks for press any key and if Ill launch it in background it will never ask it because this app measures console window and checks id output will fit?
Is such thing possible?
You should be able to do
myprocess.StandardInput.WriteLine("some input");
or
myprocess.StandardInput.Write(" ");
As I was having big problem with this kind of interactivity in powershell, I mean run console app and press key, I finally got it right and I am shearing my solution.
Press button to continue function:
Function Press-Button
{
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
}
Start process, log whole transaction and press to continue:
Function Run-Tool
{
Start-Transcript -path "C:\test.txt" -append
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = "C:\test.exe"
$ps.StartInfo.Arguments = " -verbose -page"
$ps.StartInfo.RedirectStandardInput = $true;
$ps.StartInfo.UseShellExecute = $false
$ps.start()
while ( ! $ps.HasExited )
{
Start-Sleep -s 5
write-host "I will press button now..."
Press-Button
}
Stop-Transcript
}
No matter how you do this it will be unreliable in an active system:
Function Run-Tool {
Start-Transcript -path "C:\test.txt" -append
Add-Type -AssemblyName System.Windows.Forms
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = "C:\test.exe"
$ps.StartInfo.Arguments = " -verbose -page"
$ps.StartInfo.RedirectStandardInput = $true;
$ps.StartInfo.UseShellExecute = $false
$ps.start()
do{
Start-Sleep -s 5
write-host "I will press button now..."
[System.Windows.Forms.SendKeys]::SendWait('~');
}
until($ps.HasExited)
Stop-Transcript
}
I want to see a little notification icon to indicate that the script I wrote is still active (both the script and displying the icon works). But I need a button within the context menu of the icon to stop the script immediately. And that's the part where my problem is:
$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$objContextMenu = New-Object System.Windows.Forms.ContextMenu
$ExitMenuItem = New-Object System.Windows.Forms.MenuItem
$ExitMenuItem.add_Click({
echo stoped
$continue = $False
$objNotifyIcon.visible = $False
})
$objContextMenu.MenuItems.Add($ExitMenuItem) | Out-Null
$objNotifyIcon.ContextMenu = $objContextMenu
$objNotifyIcon.Visible = $True
The script itself is longer, this is just the relevant part. If I run it from PowerShell ISE it works just fine. When I run it from a .bat file with
powershell .\myscript.ps1
the context menu is not working anymore.
This is just a wild guess, but try running the script in Single Thread Apartment mode:
powershell -STA -File .\myscript.ps1
I'm trying to save a workbook to a new location with a password and keep the filename the same. The filname gets updated weekly with a date appended to it, so it's never the same. There are two things I'm having trouble with:
Using the SaveAs method to save the file with the same name in a different path &
I can't add a password because the workbook is shared.
I'm scripting this out in PowerShell, and if possible, I'd like to unshare the workbook in the script. Unfortunately, I can't seem to find a method that accomplishes this. Here is what I have so far... I really appreciate any advice.
$xls = new-object -com excel.application
$xls.Visible = $False
$xlsWB = $xls.Workbooks.Open("path\*.xlsx")
$xlsWB.Password = "Password"
$xlsWB.SaveAs("differentPath\*.xlsx")
$xls.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xls)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWB)
Turns out, there is a method that can be called to change the sharing status of the workbook. It's ExclusiveAccess().
Here is my working code that solved the problem:
$xls = new-object -comObject excel.application
$xls.Visible = $False
$xls.DisplayAlerts = $False
$xlsWB = $xls.Workbooks.Open("FilePath")
$xlsWB.ExclusiveAccess()
$xlsWB.Password = "AddThisPassword"
$xlsWB.Save()
$xlsWB.Close()
$xls.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xls)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWB)
move "CurrentPath" "NewPath"
Once I change the sharing status of the workbook, the Password method successfully adds a password to the workbook, solving the second issue described in the OP. Rather than use SaveAs(), I decided to simply move the file, which saves me from deleting the source file.
Thanks and I hope someone finds this useful.
this was my attempt at a similar problem. It removes the MultiUserEditing of files within a folder structure.
foreach ($file in (Get-ChildItem "C:\path_to_reports\" -File -Filter "*.xls" -recurse))
{
#The filter seems to also work for *.xlxsx
$Excel = New-Object -comobject Excel.Application
$Excel.Visible = $False
$Excel.DisplayAlerts = $False
$ExcelWorkbook = $Excel.workbooks.open($file.fullname)
If ($ExcelWorkbook.MultiUserEditing -eq "True")
{
$ExcelWorkbook.ExclusiveAccess()
$ExcelWorkbook.Save()
}
#close the workbook and not the file
$ExcelWorkbook.Close()
#Quit the file
$Excel.Quit()
#cleanup
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
#more clean up
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkbook)
#the most clean up
Remove-Variable -Name excel
}
I'm trying to automate some stuff for users and one of them is to add "Computer" and "Documents" shortcut to their desktop.
I found the code below online and changed the target to "explorer.exe /e,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut(C:\users\username\Desktop\Computer.lnk")
$Shortcut.TargetPath = "explorer.exe \/e,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
$Shortcut.Save()
But when I run this code I get the following error :
"Exception calling "Save" with "0" argument : "Unable to save shotcut"
If there is another and easy way, I would love to hear it :)
Thank you everyone in advance.
I think i have a nice solution here. Also, in my own ineptitude, I think i figured out your error cause
First I found a cleaner way to make shortcuts to special folders.
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("C:\users\user\Desktop\MacadizamianNizzut.lnk")
$Shortcut.TargetPath = [environment]::getfolderpath("mycomputer")
$Shortcut.Save()
You could also use mydocuments in place of mycomputer. For a complete list of special folders that you can use: [enum]::GetNames([System.Environment+SpecialFolder]). Tips hat to JRV for a comment on my link above.
As for your error "Exception calling "Save" with "0" arguments : "Unable to save shortcut". I also got that error. In practice it was because the value passed for createshortcut was not a valid path. I am not saying that the file has to exist but the folder path does. I made a typo and got the error. Using my example this command would have failed: Test-Path ""C:\users\user\Desktop"
Some Error Prevention
What we could do is assign the shortcut path to a variable and test the path based on that.
$ShortcutPath = "C:\users\username\desktop\test.lnk"
If(Test-Path -Path (Split-Path -Path $ShortcutPath -Parent)){
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($ShortcutPath)
$Shortcut.TargetPath = [environment]::getfolderpath("mycomputer")
$Shortcut.Save()
} Else {
Write-Host "Unable to create shortcut. Check the path $ShortcutPath."
}
Back in cmd.exe, I used
set /P file=Enter path to the file:
to enter file path from console (if it wasn't supplied as a parameter), and I could press Tab to get path auto-completion. However, when I execute in Powershell
$file = Read-Host -Prompt "Enter path to the file"
then I cannot use Tab to get auto-completion, it just inserts a tabulation in the input. IS there a way to simulate the former behaviour?
I know, I know... not really an answer to your question directly, but still totally worth mentioning IMHO. Why ask the user to type out a path (and chance typos) when you can just pop up a Open File dialog box? Drop this function at the beginning of the script:
function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "All files (*.*)| *.*"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
Then when you need to get a file name and path you can just do $file = get-filename and be done with it. If you only want certain file types you can change the filter line to only allow the user to see certain kinds of files, or even a specific file name (i.e. you need them to locate 'computerlist.csv' on the hard drive or something, you can change the . in the filter to computerlist.csv).
Based on the idea of JG in SD, the version of the selection folder is given here.
function Get-FolderPath($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") > $null
$FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog
$FolderBrowserDialog.SelectedPath = $initialDirectory
$FolderBrowserDialog.ShowDialog() > $null
$FolderBrowserDialog.SelectedPath
}
Here is an updated (PSVersion 5.1 and newer) version of JG in SD's post:
function Get-FileName {
param
(
$initialDirectory
)
$null = Add-Type -AssemblyName System.windows.forms
$OpenFileDialog = New-Object -TypeName System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = 'All files (*.*)| *.*'
$null = $OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
I resolved this by using cmd.exe. I could not find a way to capture the output directly without powershell somehow disabling command completion, so I had to use Invoke-Expression and a temp file to pass back the result.
Invoke-Expression 'cmd /v:on /c set /P file=Enter target path: `& if defined file echo !file! `> %TEMP%\temp.tmp'
$TargetPath = $null
If ( Test-Path -PathType Leaf "$ENV:TEMP\temp.tmp" ) { $TargetPath = (Get-Content "$ENV:TEMP\temp.tmp").Trim() 2>$null }
Remove-Item "$ENV:TEMP\temp.tmp" 2>$null