I have a program which needs to be installed silently. But it doesn't stop the process at the end (because of window - need to press OK). So I need to:
Check if the folder exists
Start the installation of the program and wait for ~15 seconds
Then I need to check one of the files in this folder of the program
If it exists I should kill the process of installation (if not go to 1.)
So I have two script blocks. How can I realize it to one script block or maybe function?
if (-not(Test-Path $folder)) {
Start-Process $program /S
} else {
Write-Output "Program is already installed"
}
if (Test-Path $file) {
Stop-Process -Name "Install program*"
} else {
Write-Output "Program is already installed"
}
Nest the second conditional in the "then" branch of the first one, assign the installer process object to a variable, add a sleep after starting the process, and kill the process via the process object.
if (-not(Test-Path $folder)) {
$p = Start-Process $program /S -PassThru
Start-Sleep -Seconds 15
if (Test-Path $file) {
$p.Kill()
} else {
Write-Output "Program is already installed"
}
} else {
Write-Output "Program is already installed"
}
Related
I have a PS script to monitor a logging file for a specific list of servers in the network. If the script logic finds the issue I'm monitoring for, I want to interrupt the script process with a launch and wait of windows explorer for the related server network folder path.
Windows explorer does show the requested folder, however PS doesn't wait for it to close.
This is the script I'm testing with:
# Sample networkfolderpath
$networkfolderpath = '\\server\d$\parent\child'
Start-Process explorer.exe -ArgumentList $networkfolderpath -Wait
FYI: I have a RDP function that is setup the same way and it does wait as expected.
Start-Process mstsc /v:$computername -Wait
I'm presuming at this point that windows explorer just behaves differently than some other exe.
What am I missing?
As mentioned in the comments, the example with mstsc only works because there's a 1-to-1 relationship between closing the main window it produces and exiting the process.
This relationship does not exist with explorer - it'll detect on launch that the desktop session already has a shell, notify it of the request, and then exit immediately.
Instead, use Read-Host to block further execution of the script until the user hits enter:
# launch folder window
Invoke-Item $networkfolderpath
# block the runtime from doing anything further by prompting the user (then discard the input)
$null = Read-Host "Edit the files, then hit enter to continue..."
Here is some proof-of-concept code that opens the given folder and waits until the new shell (Explorer) window has been closed.
This is made possible by the Shell.Application COM object. Its Windows() method returns a list of currently open shell windows. We can query the LocationURL property of each shell window to find the window for a given folder path. Since there could already be a shell window that shows the folder we want to open, I check the number of shell windows to be sure a new window has been opened. Alternatively you could choose to bring an existing shell window to front. Then just loop until the Visible property of the shell window equals $False to wait until the Window has been closed.
Function Start-Explorer {
[CmdletBinding()]
param (
[Parameter(Mandatory)] [String] $Path,
[Parameter()] [Switch] $Wait
)
$shell = New-Object -ComObject Shell.Application
$shellWndCountBefore = $shell.Windows().Count
Invoke-Item $Path
if( $wait ) {
Write-Verbose 'Waiting for new shell window...'
$pathUri = ([System.Uri] (Convert-Path $Path)).AbsoluteUri
$explorerWnd = $null
# Loop until the new shell window is found or timeout (30 s) is exceeded.
foreach( $i in 0..300 ) {
if( $shell.Windows().Count -gt $shellWndCountBefore ) {
if( $explorerWnd = $shell.Windows() | Where-Object { $_.Visible -and $_.LocationURL -eq $pathUri }) {
break
}
}
Start-Sleep -Milliseconds 100
}
if( -not $explorerWnd ) {
$PSCmdlet.WriteError( [Management.Automation.ErrorRecord]::new(
[Exception]::new( 'Could not find shell window' ), 'Start-Explorer', [Management.Automation.ErrorCategory]::OperationTimeout, $Path ) )
return
}
Write-Verbose "Found $($explorerWnd.Count) matching explorer window(s)"
#Write-Verbose ($explorerWnd | Out-String)
Write-Verbose 'Waiting for user to close explorer window(s)...'
try {
while( $explorerWnd.Visible -eq $true ) {
Start-Sleep -Milliseconds 100
}
}
catch {
# There might be an exception when the COM object dies before we see Visible = false
Write-Verbose "Catched exception: $($_.Exception.Message)"
}
Write-Verbose 'Explorer window(s) closed'
}
}
Start-Explorer 'c:\' -Wait -Verbose -EA Stop
I am not quite sure how to explain my problem, but I have a function that installs Office, imagine the person that runs this script does not have internet connection or does not have enough space on her hard drive. I have the XML file set to hide the setup interface so the user can't see the installation process. Just to be clear all my code works fine, just want add this feature so that if something goes wrong while the user runs the script I know where the error was.
This is my function:
Function Install-Office365OfficeProducts{
Write-Host ""
Start-Sleep -Seconds 5
Write-Host "Installing Office 365 ProPlus..."
# Installing Office 365 ProPlus
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
This is what I have tried:
if (Install-Office365OfficeProducts -eq 0) {
Write-Host "FAILED"}
I am very confused, I thought that a function that runs with no error returns 1 and when it runs with errors returns 0.
Also have tried to put the code like this:
try {
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
} catch {
Write-Host "Failed!"
}
EDIT:
Basically i want to be shown an error if the Office setup is not finished...
#Thomas
Function Install-Office365Product{
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
try{
Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -NoNewWindow -ErrorAction Stop
}catch{
Write-Host "It was not possible to install the product!"
}
}
Your try/catch-block inside Install-Office365OfficeProducts is useless, because Install-Office365Product will not throw anything, except you pass wrong arguments. The try/catch-block inside Install-Office365Product will most likely also not catch anything. But you can of course evaluate the return code of your installer called with Start-Process:
function Install-Office365Product {
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
$process = Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Installation successful"
} else {
Write-Host "Installation failed"
}
}
Instead of writing to stdout, you can of course also throw an exception and handle it later in a higher function.
I wrote a script which will
stop different services of two or more servers (one server containing two services),
take a back up of respected folders (folder name starting with Job) to backup folder,
copy newest files from staging location to destination folder,
start different services in servers.
Looks like I have chosen long procedure to do it. Please suggest if I can modify the script.
Function Get-Kettle
{
$DestinationFolder = "D:\AppCode\Kettle"
$BackUpFolder = "D:\AppCode\Kettle\BackUp"
$StagingFolder = "\\server001\e$\Packages\RTO\RTO\ETL"
$ServerList = #("server222")
$ServicesList = #("WindowsScheduler","WindowsSchedulerLogon")
Foreach ($Server in $ServerList)
{
$CheckStagingFolder = Get-ChildItem $StagingFolder
if($CheckStagingFolder.count)
{
Write-Host "StagingFolder contains files.. Continue with Deployment"
if((Test-Path "$DestinationFolder") -and (Test-Path "$BackUpFolder"))
{
Write-Host "Taking BackUp"
Copy-Item "$DestinationFolder" -Destination "$BackUpFolder"
Write-Host "BackUp is completed"
Write-Host "Stopping the service WindowsSchedulerLogon"
Stop-Service $Server.WindowsSchedulerLogon -Force
Write-Host "WindowsSchedulerLogon service is stopped"
Write-Host "Stopping the service WindowsScheduler"
Stop-Service WindowsScheduler -Force
Write-Host "WindowsScheduler service is stopped"
Copy-Item "$StagingFolder" -Destination "$DestinationFolder" -Recurse
Write-Host "Starting the service WindowsSchedulerLogon"
Start-Service WindowsSchedulerLogon -Force
Write-Host "WindowsSchedulerLogon service is Started"
Write-Host "Starting the service WindowsScheduler"
Start-Service WindowsScheduler -Force
Write-Host "WindowsScheduler service is started"
Write-Host "Deployment is completed"
}
else
{
Write-Host "No Destination and BackUp Folder..Script Exiting...!"
Exit;
}
}
else
{
Write-Host "StagingFolder does not contains files.. Exiting with Deployment"
exit;
}
}
}
Get-Kettle
I agree this would be better placed on Code Review but as it's still open here I thought I'd suggest some improvements:
Add [cmdletbinding()] and put your variables at the top in a Param() block. Cmdletbinding means you get access to a set of common parameters that allow you to utilise a series of builtin functionality (such as Write-Verbose which I will mention later). Putting your variables in a Param block means you can change them by calling them in your Function, just like you do with built-in cmdlets.
Add Begin, Process and End blocks. I can't see any need to do anything in Begin or End at this point, but the rest of your code can live in Process. This means you could later change this function to support pipeline input if you wanted to.
Change all your Write-Host statements to Write-Verbose. Write-Host is considered harmful because it interferes with automation. Write-Verbose means you can see information messages when you want to but not by default, and those messages to a separate output stream that doesn't then interfere with the output of the function. To see the messages simply do Get-Kettle -Verbose. A further benefit of this is that it will also turn on the -Verbose messages that are built in to the standard cmdlets you are using (such as Copy-Item or Start-Service).
You can should change your last two Write-Host statements (in the else blocks) to Write-Warning messages, these will then always be displayed (regardless of -verbose etc.) when you need to inform the user of something but again, do not interfere the default output pipeline.
You can drop the Exit statements as they not really achieving anything.
Other improvements you might consider include:
Accepting input from the pipeline.
Parameter sets or validating on your parameters and setting variable types on them (such as [string] etc.).
Supporting -confirm and -whatif by adding supportsshouldprocess and putting these around the parts of the script that make changes.
All of these things are features of what is called an Advanced function or Cmdlet. If you haven't read it, take a look at Learn PowerShell Toolmaking in a Month of Lunches as it covers these topics and a lot more.
Here's a copy of your code with the first set of improvements I suggested implemented:
Function Get-Kettle
{
[cmdletbinding()]
Param(
$DestinationFolder = "D:\AppCode\Kettle",
$BackUpFolder = "D:\AppCode\Kettle\BackUp",
$StagingFolder = "\\server001\e$\Packages\RTO\RTO\ETL",
$ServerList = #("server222"),
$ServicesList = #("WindowsScheduler","WindowsSchedulerLogon")
)
Begin {}
Process {
Foreach ($Server in $ServerList)
{
$CheckStagingFolder = Get-ChildItem $StagingFolder
if($CheckStagingFolder.count)
{
Write-Verbose "StagingFolder contains files.. Continue with Deployment"
if((Test-Path "$DestinationFolder") -and (Test-Path "$BackUpFolder"))
{
Write-Verbose "Taking BackUp"
Copy-Item "$DestinationFolder" -Destination "$BackUpFolder"
Write-Verbose "BackUp is completed"
Write-Verbose "Stopping the service WindowsSchedulerLogon"
Stop-Service $Server.WindowsSchedulerLogon -Force
Write-Verbose "WindowsSchedulerLogon service is stopped"
Write-Verbose "Stopping the service WindowsScheduler"
Stop-Service WindowsScheduler -Force
Write-Verbose "WindowsScheduler service is stopped"
Copy-Item "$StagingFolder" -Destination "$DestinationFolder" -Recurse
Write-Verbose "Starting the service WindowsSchedulerLogon"
Start-Service WindowsSchedulerLogon -Force
Write-Verbose "WindowsSchedulerLogon service is Started"
Write-Verbose "Starting the service WindowsScheduler"
Start-Service WindowsScheduler -Force
Write-Verbose "WindowsScheduler service is started"
Write-Verbose "Deployment is completed"
}
else
{
Write-Warning "No Destination and BackUp Folder..Script Exiting...!"
}
}
else
{
Write-Warning "StagingFolder does not contains files.. Exiting with Deployment"
}
}
}
End {}
}
Get-Kettle -Verbose
I have a Powershell script that uses the VAppLauncher program to launch WinSCP three times throughout the code to move files from Windows to Unix.
I was wondering if there is a command to wait/pause the Powershell script until the virtual WinSCP console is finished and the window closes. Currently, the code just ploughs through the steps and doesn't wait for this to finish.
The call to the app:
C:\Windows\SysWOW64\CCM\VAppLauncher.exe /launch "WinSCP 4.0.6.358" /console /script=$aScript /log=$aLog
The full code:
Try {
C:\Windows\SysWOW64\CCM\VAppLauncher.exe /launch "WinSCP 4.0.6.358" /console /script=$aScript /log=$aLog
}
Catch [Exception] {
Add-Content $tempLog "$a - System exception running $putScript:"
Add-Content $tempLog $_.Exception.Message
}
Finally {
Add-Content $tempLog "$a - WinSCP ran $putScript successfully"
}
The easiest way to do it is to pipe the instance of WinSCP to Out-Null this will stall the script until WinSCP closes.
Try
{
C:\Windows\SysWOW64\CCM\VAppLauncher.exe /launch "WinSCP 4.0.6.358" /console /script=$aScript /log=$aLog | Out-Null
}
Catch [Exception]
{
Add-Content $tempLog "$a - System exception running $putScript:"
Add-Content $tempLog $_.Exception.Message
}
Finally
{
Add-Content $tempLog "$a - WinSCP ran $putScript successfully"
}
Notice the Out-Null at the end of the try block. You could also use background jobs if you wanted to.
I'm new to powershell, but I'm trying to output some simple logging in a ps I'm writing to create scheduled tasks. My code is below. It seems that it doesn't throw an exception when you get an error with schtasks. Another SO question mentioned this with fileIO actions and suggested doing "-ea stop" but that doesn't work with schtasks.
#create log file
$log = "\\servername\!incoming\Deploy\log.txt"
Clear-Content $log
#get input file list
$txtServerList = Gc "\\servername\!incoming\Deploy\serverlist.txt"
#loop through each server
ForEach($strServername In $txtServerList)
{
try
{
#install task from XML template
schtasks /create /s $strServername /tn InventoryServer /XML "\\servername\!incoming\Deploy\TaskTemplate.xml"
#run the task immediately
schtasks /run /s $strServername /tn InventoryServer
}
catch [exception]
{
Add-Content -path $log -value $strServername
#Add-Content -path $log -value $_.Exception
#Add-Content -path $log -value $_.Exception.Message
#Add-Content -path $log -value ""
}
}
I verified that 'Add-Content -path "\servername!incoming\Deploy\log.txt" -value "test"'works, so like I said I'm fairly sure it's just not throwing an exception.
In order for a Try/Catch to work, PowerShell needs a terminating exception. When running a cmdlet in a Try block you can make that happen by using -erroraction Stop (or use the -ea alias). As you already realize SCHTASKS.EXE can't do this. Without a terminating exception, the code in the Catch block will never run.
What you have to do is step out side the box, so to speak, and independently check if Schtasks failed. If so, they you can use Write-Error in your Try block.
One thing you might try is using Start-Process and look at the exit code. Anything other than 0 should be an error.
Try {
get-date
$p=Start-Process schtasks.exe -ArgumentList "/Create foo" -wait -passthru
if ($p.exitcode -ne 0) {
write-error "I failed with error $($p.exitcode)"
}
}
Catch {
"oops"
$_.Exception
}