I'm running a Windows Service (Hudson) which in turn spawns a PowerShell process to run my custom PowerShell commands. Part of my script is to unzip a file using CopyHere. When I run this script locally, I see a progress dialog pop up as the files are extracted and copied. However, when this runs under the service, it hangs at the point where a dialog would otherwise appear.
Here's the unzip portion of my script.
# Extract the contents of a zip file to a folder
function Extract-Zip {
param([string]$zipFilePath, [string]$destination)
if(test-path($zipFilePath)) {
$shellApplication = new-object -com shell.application
$zipFile = get-item $zipFilePath
$zipFolder = $shellApplication.NameSpace($zipFile.fullname)
$destinationFile = get-item $destination
$destinationFolder = $shellApplication.NameSpace($destinationFile.fullname)
$destinationFolder.CopyHere($zipFolder.Items())
}
}
I suspect that because its running under a service process which is headless (no interaction with the desktop), its somehow stuck trying to display a dialog.
Is there a way around this?
If it's still actual, I managed to fix this with having CopyHere params equal 1564.
So in my case extract zip function looks like:
function Expand-ZIPFile{
param(
$file, $destination
)
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)
foreach($item in $zip.items())
{
$shell.Namespace($destination).copyhere($item,1564)
"$($item.path) extracted"
}
1564 description can be found here - http://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx:
(4) Do not display a progress dialog box.
(8) Give the file being operated on a new name in a move, copy, or rename operation if a file with the target name already exists.
(16) Respond with "Yes to All" for any dialog box that is displayed.
(512) Do not confirm the creation of a new directory if the operation requires one to be created.
(1024) Do not display a user interface if an error occurs.
If this is running on Vista or Windows 7, popping up UI from a service isn't going to be seen by the end user as you suspected. See this paper on Session 0 Isolation. However, does the progress dialog require user input? If not, I wouldn't think that would cause the service to hang. I would look for an option to disable the progress display. If you can't find that, then try switching to another ZIP extractor. PSCX 1.2 comes with an Expand-Archive cmdlet. I'm sure there are also others available.
Looking at the documentation for PowerShell, it looks like the -NonInteractive option may help here
Related
I've written some scripts to perform a set of remote deployments. I do this by executing a script locally, which does an Invoke-Command to trigger a script to run on the remote server. This deploys a Windows Service with supplied credentials.
This all works great for the most part, and saves us (me) a whole load of time. However, I occasionally hit an issue which manifests itself with a Prompt (alert/dialog box etc) that I cannot seem to get around.
If I log onto the remote machine and run the script, I can see the prompt appear. It says something along the lines of "This action cannot be completed because the file is open in another process". The actual issue itself is probably race condition related, as I can get around it just by dismissing the prompt, but the problem is that I cannot "get the alert" remotely - the script at my end just hangs.
What I'd really like to do is handle the Alert, regardless of what it says and what the options are, I'd like to either "bring it back" to the remote end so to speak, or at least have some sort of notification that it's blocking the operation with some way to dismiss it.
Here's an example of it:
EDIT:
We have two machines (A), and (B). (A) is my machine, (B) is the remote server.
On (A), I have a script [let's call it RemoteExecute.ps1], which contains something like this:
Invoke-Command -Session $session -Scriptblock { c:\updates\Deploy.ps1 }
C:\updates is a location on (B), and Deploy.ps1 is a script in that location. Deploy.ps1 is the script which triggers the Alert on (B). If I log onto (B) via Remote Desktop and execute Deploy.ps1 locally on that machine, it gives me the Alert on screen.
However, when I run this script remotely from (A), via the RemoteExecute.ps1 script, I don't see the Alert that's generated on (B) and I have no way of even knowing that an Alert is blocking things. My my end on (A) all I see is the RemoteExecute script hanging indefinitely.
How can I detect that there's an Alert waiting to be dismissed on (B)?
EDIT 2:
#Extract from Deploy script:
Write-Host 'Deploying files ...'
if (!(Test-Path $targetpath)) {
mkdir $targetpath
}
Expand-ZIPFile "$releasepath\$ExeName.zip" $targetpath
Write-Host 'Files deployed'
...
#Function from another file:
function Expand-ZIPFile($file, $destination)
{
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)
$destFolder = $shell.Namespace($destination)
if (!(test-path $file)) { throw "$file does not exist" }
foreach($item in $zip.items())
{
$shell.Namespace($destination).copyhere($item, 0x14)
}
}
Until recently, we've been deploying .exe applications by simply copying them manually to the destination folder on the server. Often though, the file was already running at the time of deployment (the file is called from a SQL Server job)--sometimes even multiple instances. We don't want to kill the process while it's running. We also can't wait for it to finish because it keeps on being invoked, sometimes multiple times concurrently.
As a workaround, what we've done is a "cut and paste" via Windows Explorer on the .exe file into another folder. Apparently, what this does is it moves the file (effectively a delete) but keeps it in RAM so that the processes which are using it can continue without issues. Then we'd put the new files there which would get called when any later program would call it.
We've now moved to an automated deploy tool and we need an automated way of doing this.
Stop-Process -name SomeProcess
in PowerShell would kill the process, which I don't want to do.
Is there a way to do this?
(C# would also be OK.)
Thanks,
function moverunningprocess($process,$path)
{
if($path.substring($path.length-1,1) -eq "\") {$path=$path.substring(0,$path.length-1)}
$fullpath=$path+"\"+$process
$movetopath=$path + "--Backups\$(get-date -f MM-dd-yyyy_HH_mm_ss)"
$moveprocess=$false
$runningprocess=Get-WmiObject Win32_Process -Filter "name = '$process'" | select CommandLine
foreach ($tp in $runningprocess)
{
if ($tp.commandline -ne $null){
$p=$tp.commandline.replace('"','').trim()
if ($p -eq $fullpath) {$moveprocess=$true}
}
}
if ($moveprocess -eq $true)
{
New-Item -ItemType Directory -Force -Path $movetopath
Move-Item -path "$path\*.*" -destination "$movetopath\"
}
}
moverunningprocess "processname.exe" "D:\Programs\ServiceFolder"
Since you're utilizing a SQL Sever to call the EXE. Why do you add a table that contains the path to the latest version of the file and modify your code that fires the EXE. That way when a new version is rolled out, you can create a new folder, place the file in it, and update the table pointing to it. That will allow any still active threads to have access to the old version and any new threads will pickup up the new executable. You then can delete the old file after it's no longer needed.
A little background about what I'm trying to do and why. We are slowly migrating from using a local copy of office on our end users Win7 machines to publishing office through RDS 2012. With 2012 you can have the end users machine subscribe to a webfeed which puts the shortcuts to the actual RDP files in Appdata. In order to have our image pretty much mirror before RDS, I need to pin the shortcuts to the taskbar. If you pin the shortcut as it comes from the RDS Gateway server the icon on the taskbar is that of the .rdp file. If you edit the target for the shortcut and put mstsc.exe before the path to the .rdp file you can then pin the shortcut to the taskbar using those icons of the shortcut.
I have found posts on how to change the target field of shortcuts but nothing on how to add something to what is currently there. An environment variable is needed since the path to the shorts will be different for each user. Below is I have tried thus far
$Word = $env:userprofile + "\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\RemoteApp and Desktop Connections\Microsoft Word 2010.lnk"
$sh = New-Object -COM WScript.Shell
$targetPath = $sh.CreateShortcut($Word).TargetPath
$sh.TargetPath = "mstsc.exe" + $targetPath ## Make changes
$sh.Save() ## Save$shell = New-Object -COM WScript.Shell
One of the errors i'm getting is : Property 'Arguments' cannot be found on this object; make sure it exists and is settable.
Any help will be greatly appreciated.
Instead of using Shell COM object, how about using a .Net wrapper class? There is a great sample.
To use the VBAccelerator's wrapper in Powershell, extract the source code and compile a DLL like so,
C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /t:library /out:ShellLink.dll /r:System.Drawing.dll .\ShellLink.cs .\FileIcon.cs
This should create ShellLink.dll, which you can add as Powershell type like so,
Add-Type -path .\ShellLink.dll
And use the class by creating a new object like so,
$lnk = new-object vbAccelerator.Components.Shell.ShellLink
$lnk.Target = "C:\Windows\System32\mstsc.exe"
$lnk.Arguments = "some arguments"
$lnk.Description = "My awesome shortcut"
$lnk.Save("c:\temp\test.lnk")
Your code will prepend "mstsc.exe" to the current target path if you just add this line before your ## Make changes line:
$sh = $sh.CreateShortcut($word)
Sounds like you also want to add a space, so that your lnk file is the "connection file" argument of mstsc.exe. You can set the Arguments property like so:
function Set-RDSshortcut {
param( [parameter(mandatory=$true)]$Shortcut )
$sh = New-Object -COM WScript.Shell
$targetPath = $sh.CreateShortcut($Shortcut).TargetPath
$targetPath = "mstsc.exe"
$sh = $sh.CreateShortcut($Shortcut)
$sh.TargetPath = $targetPath
$sh.Arguments = $Shortcut
$sh.Save()
} #end function
I'm trying to create a shortcut on a remote desktop in the domain here, and I'm a domain admin. If I run the following code directly on the target machine, the shortcut can be created and is able to lead me to the target path.
$shortcutpath3 = "c:\Users\Public\Desktop\Shortcuts to Test Custom\VV 1211 -TC.lnk"
$WshShell3 = New-Object -comObject WScript.Shell
$Shortcut3 = $WshShell3.CreateShortcut($shortcutpath3)
$Shortcut3.TargetPath = "\\machine\testcustom\"
$Shortcut3.Save()
I saved this script as test.ps1, run it with folloing code on a different mahchine. The code ends without any errors/warings, and the shortcut is created on the target machine with the propeties i specified. But it cannot lead me to the target place, it actually ask me to pick a program to open that file. I compared the properties of the 2 shortcuts, and found that the "target type" of the broken shortcut is "file" while it is "file folder" for a good shortcut.
Invoke-Command -ComputerName TARGETSERVER -FilePath test.ps1
Any idea how i can fix this? And why is this happening? Thank!!!
I had the same problem and and I used Get-Item to make it work. Try this:
$targetPath = Get-Item("\\machine\testcustom\")
$WshShell3 = New-Object -comObject WScript.Shell
$Shortcut3 = $WshShell3.CreateShortcut($shortcutpath3)
$Shortcut3.TargetPath = $targetPath.FullName
$Shortcut3.Save()
Since you're a domain admin I'd strongly recommend doing this with a Group Policy Preference. You can restrict shortcut creation to particular users/groups/computers/etc. via item-level targeting.
I've been battling this for the past several hours, Googling to no avail. For posterity, here's my summary. While alternatives suggestions are appreciated:
PowerShell inexplicably has no direct way to create a shortcut. It can create symbolic link, but 1) it requires admin rights, and 2) it behaves differently.
Group Policy Preferences are great--but only if your machines are in office or routinely on a VPN.
If you're trying to create a shortcut to a network folder, setting the TargetPath to that folder only works if the computer actually can reach it when the script runs, i.e. same issue as with Group Policy Preferences. PowerShell will create the shortcut, but the Target Type will be a File rather than a File Folder (and I found no info online how to control that; trailing slash or not doesn't matter).
The behavior of running these code suggestions varies depending whether you run this interactively or as a script.
Here's what I found does work:
Set the $shortcut.TargetPath to "C:\Windows\Explorer.exe"
Add a line: $shortcut.Arguments = """Target folder""" (the """ are needed as an escape character for the shortcut to show as C:\Windows\Explorer.exe "Target folder")
So for your example above, the following should work (assuming permissions to c:\Users\Public\Desktop):
$shortcutpath3 = "c:\Users\Public\Desktop\Shortcuts to Test Custom\VV 1211 -TC.lnk"
$WshShell3 = New-Object -comObject WScript.Shell
$Shortcut3 = $WshShell3.CreateShortcut($shortcutpath3)
$Shortcut3.TargetPath = "C:\Windows\Explorer.exe"
$shortcut.Arguments = """\\machine\testcustom\"""
$Shortcut3.Save()
I'm installing a file through powershell silently, but would like to give feedback on the progress of the installation. I can't seem to find this information anywhere. This is the code I have for running the exe:
$exe = "wls1033_oepe111150_win32.exe"
$xmlLocation = Resolve-Path "silent_install.xml"
$xmlLocation = "-silent_xml=" + $xmlLocation
$installLogLoc = Resolve-Path "wls_install.log"
$installLogLoc = "-log=" + $installLogLoc
$AllArgs = #('-mode=silent', $xmlLocation, $installLogLoc)
$filePath = Resolve-Path $exe
$p = New-Object System.Diagnostics.Process
$p.StartInfo.Filename = $filePath
$p.StartInfo.Arguments = $AllArgs
$p.Start();
$p.WaitForExit();
Is there even a way to do this? I do get a progress meter for the extraction process in an alternate command window that installs the exe, but other than that it sits for there about 10 minutes with no sort of indication.
Edit: So seeing as this isn't possible, is there a way to do an asynchronous pipeline call while running the exe?
Thanks
Please correct me if I'm wrong, but I'm 99% sure that this is NOT possible. Your exe file is seperate process and not a PowerShell script. It will not be able to pass status messages to your PowerShell sessions. The only possibility would be to detect the setups log-file, tail it and update a progressbar in PowerShell based on keywords from the log. This is however a big task and something you need to customize for each setup file.
I would try to look into your exe file and see if it has a "basic ui" (or a similar type) mode that you can use instead of the silent option. A last alternative would be to repackage the setup with such an option(making installation automated with only progressbar). This solution would still only show the progressbar in a separate window and not in PowerShell itself.