Trying to run a chkdsk on c:\ automatically - powershell

Trying to run a chkdsk on all drives of the computer. Having issues with having C:\ start at all.
Trying to use SendKeys to answer "Would you like to schedule this volume to be checked the next time the system restarts? (Y/N)" but having no luck. Where am I going wrong?
$host.ui.RawUI.WindowTitle = "BOHCdrivefix"
$FixCDrive = Start-Job -Name cDriveFix -ScriptBlock {chkdsk c:/f}
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('BOHCdrivefix')
sleep 3
$wshell.SendKeys('y')
$wshell.SendKeys('~')
wait-job $FixCDrive
Receive-Job $FixCDrive | Out-File -FilePath D:\temp\cDriveFix.txt
shutdown -r -f -t 0
I would like to answer Y to the question then shutdown the PC and have it start the chkdsk

From Start-Job document:
The Start-Job cmdlet starts a Windows PowerShell background job on
the local computer.
A Windows PowerShell background job runs a command without
interacting with the current session.
Hence, you can't send any key from the current session to a command running inside a background job. Sending a key must be inside the background job. Fortunately, chkdsk.exe accepts use pipeline operator (which sends the results of the preceding command to the next command) as follows:
$FixCDrive = Start-Job -Name cDriveFix -ScriptBlock {Write-Output 'y'|chkdsk.exe c:/f}
or (using echo alias for Write-Output cmdlet):
$FixCDrive = Start-Job -Name cDriveFix -ScriptBlock {echo 'y'|chkdsk.exe c:/f}
Please note:
The type of the file system is NTFS.
Cannot lock current drive.
Chkdsk cannot run because the volume is in use by another
process. Would you like to schedule this volume to be
checked the next time the system restarts? (Y/N)
To answer "yes" to above question asked by chkdsk c:/f (fix file system errors on the boot partition), you must press Y, followed by Enter.
Honestly said, I'm not sure whether Write-Output cmdlet sends Enter into the pipeline. If not, force output of the new line as follows:
Write-Output "y$([System.Environment]::NewLine)"|chkdsk.exe c:/f}

Related

My powershell script is not stopping a process that I am opening

I am creating a basic powershell script that will open the calculator app, get its process id, and then eventually stop the process itself with confirmation. But when I run the following script on my computer (through Powershell ISE, but it also doesn't seem to work on regular powershell in admin mode), it does not kill the calculator process. I am running Windows 10 Version 21H1.
Start-Process calc.exe; Get-Process calc; Stop-Process -Name calc -whatif; Stop-Process -Name calc -Confirm -PassThru
I had to use the name Calculator instead of calc for Get-Process. I also had to add a delay between the launching of calc.exe and Stop-Process. Without the delay, it could not find the process to stop it.
Start-Process -FilePath calc.exe
Start-Sleep -Seconds 3
Get-Process -Name Calculator | Stop-Process

Trying to loop a set of commands in PowerShell

I am new to scripting in general and am trying to tackle the task of writing a PowerShell script to automate accepting RSA keys from PuTTY across some 15,000 servers in my organization. I have the servers saved in a .bat file and when running that it will auto login through PuTTY. The issue is when it logs in A RSA security window will pop up requiring me to hit "y" I have that part and closing PuTTY so the next instance will be loaded, the only issue is I cant get the process to loop. I am looking for some guidance on the issue.
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
#Variables
$batFile = Start-Process -FilePath "C:\Users\UID\OneDrive - CompanyA\PS Scripts\puttyRSA.bat";
New-Object -ComObject wscript.shell;
#opens the "puttyRSA.bat" file
$batFile
#Loops everything
do{
# Will click "Y"
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('title of the application window')
Start-Sleep -Seconds 3
$wshell.SendKeys('y')
#Waits and closes putty
Start-Sleep -Seconds 3
Stop-Process -name putty
}
While (-FilePath puttyRSA.bat=running)here
Assuming you want to keep running until $batFile finishes, your while clause isn't valid PowerShell. You'll have to make two changes here.
First, you'll need to kick off $batFile with Start-Process so you can get the PID to wait on:
# -PassThru is required because by default Start-Process doesn't return an object
$processId = ( Start-Process -FilePath $batFile -PassThru ).Id
Then, for your while clause:
} while ( Get-Process -Id $processId 2>$null )
This will keep your loop running until the process belonging to $batFile ends. The 2>$null redirects the error stream to $null, so it won't display an error when the process can no longer be found.
You can read more on output streams and redirection on my answer here.

Is there a way to warn remote computer of pending restart AND have my script wait for the restart to complete?

I'm looking to combine the "You will be logged off in x minutes" functionality of 'shutdown.exe", and the "-Wait -For PowerShell" functionality of the Restart-Computer cmdlet.
I have a script that requires a remote computer to restart. I'd like to warn the user of the remote computer that their computer will restart in 5 minutes. I would also like my script to wait until the restart completes before continuing forward.
Shutdown.exe has a great warning system and delay built in, but no great way to have the script wait for the reboot to complete. I've tried a loop that waits until Test-Connection is $true, but of course a computer returns a ping before it can accept PowerShell commands like Invoke-Command. I could slap a Start-Sleep on the end, but the time between ping and PowerShell accepting commands varies per computer.
Restart-Computer has a great "wait for PowerShell" feature, but there's no way to warn the user of the remote computer that their computer is about to restart. I've tried running the commands below, but the restart-computer cmdlet will throw an error if there's already a shutdown in progress.
shutdown /r /t 300 /m \\computer $system
restart-computer -computername $system -Force -Wait -For PowerShell -Timeout 300 -Delay 2
You can try just sending the message just before your call to Restart-Computer using the msg command:
msg * /SERVER:$system /TIME:300 "Computer will be restarted in 5 minutes."
restart-computer -computername $system -Force -Wait -For PowerShell -Delay 2
I won't say this is a complete answer to your question, but instead of Test-NetConnection (which only tests ping/TCP as you mentioned), use Test-WSMan which will test the protocol over which PowerShell Remoting operates (note other protocols are possible, but you're likely just using this one in your environment).

Powershell build step, fire and forget?

I am running the following powershell command in a build step using TFS 2018.
Start-Job -ScriptBlock {
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
}
Since I don't want the script to affect the build step it should simply fire and forget the script. Hence I am using Start-Job. But it seems that once the step is done the process is killed. Is there a way to maintain the process lifetime even though the build step is done or the build process is finished?
Additional information... the powershell script should run on the remote server. The script itself triggers an .exe with parameters.
To simply fire and forget, invoke the script with Invoke-Command -AsJob:
Invoke-Command -AsJob -FilePath \\MyServer\run.ps1 -ComputerName MyServer -Args arg1, arg2
Start-Sleep 1 # !! Seemingly, this is necessary, as #doorman has discovered.
This should kick off the script remotely, asynchronously, with a job getting created in the local session to monitor its execution.
Caveat: The use of Start-Sleep - possibly with a longer wait time -
is seemingly necessary in order for the remote process to be created before the calling script exits, but such a solution may not be fully robust, as there is no guaranteed timing.
Since you're not planning to monitor the remote execution, the local session terminating - and along with it the monitoring job - should't matter.
When do you want the script to stop running? You could use a do-while loop and come up with a <condition> that meets your needs.
Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while(<condition>)
}
Alternatively, you could use the condition $true so it executes forever. You will have to stop the job later in the script when you no longer need it.
$job = Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while($true)
}
Stop-Job $job
Remove-Job $job
I've added a Start-Sleep 2 so it doesn't lock up your CPU as no idea what the script is doing - remove if not required.
Why not something like this:
Invoke-Command -Filepath \\MyServer\Run.ps1 -Computername MyServer -Argumentlist Arg1,Arg2 -AsJob
$JobCount = (get-job).Count
Do
{
Start-Sleep -Seconds 1
$totalJobCompleted = (get-job | Where-Object {$_.state -eq "Completed"} | Where-Object {$_.Command -like "NAMEOFCOMMAND*"}).count
}
Until($totalJobCompleted -ge $JobCount)
#doorman -
PowerShell is natively a single threaded application. In almost all cases, this is a huge benefit. Even forcing multiple threads, you can see the child threads are always dependent on the main thread. If this wasn't the case, it would be very easy to create memory leaks. This is almost always a good thing as when you close the main thread, .Net will clean up all the other threads you may have forgotten about for you. You just happened to run across a case where this behaviour is not beneficial to your situation.
There are a few ways to tackle the issue, but the easiest is probably to use the good ol' command prompt to launch an independent new instance not based at all on your original script. To do this, you can use invoke-expression in conjunction with 'cmd /c'. See Below:
invoke-expression 'cmd /c start powershell -NoProfile -windowstyle hidden -Command {
$i = 0
while ($true) {
if($i -gt 30) {
break
}
else {
$i | Out-File C:\Temp\IndependentSessionTest.txt -Append
Start-Sleep -Seconds 1
$i++
}
}
}
'
This will start a new session, run the script you want, not show a window and not use your powershell profile when the script gets run. You will be able to see that even if you kill the original PowerShell session, this one will keep running. You can verify this by looking at the IndependentSessionTest.txt file after you close the main powershell window and see that the file keeps getting updated numbers.
Hopefully this points you in the right direction.
Here's some source links:
PowerShell launch script in new instance
How to run a PowerShell script without displaying a window?

How do I run a Windows installer and get a succeed/fail value in PowerShell?

I would like to install a set of applications: .NET 4, IIS 7 PowerShell snap-ins, ASP.NET MVC 3, etc. How do I get the applications to install and return a value that determines if the installation was successful or not?
These answers all seem either overly complicated or not complete enough. Running an installer in the PowerShell console has a few problems. An MSI is run in the Windows subsystem, so you can't just invoke them (Invoke-Expression or &). Some people claim to get those commands to work by piping to Out-Null or Out-Host, but I have not observed that to work.
The method that works for me is Start-Process with the silent installation parameters to msiexec.
$list =
#(
"/I `"$msi`"", # Install this MSI
"/QN", # Quietly, without a UI
"/L*V `"$ENV:TEMP\$name.log`"" # Verbose output to this log
)
Start-Process -FilePath "msiexec" -ArgumentList $list -Wait
You can get the exit code from the Start-Process command and inspect it for pass/fail values. (and here is the exit code reference)
$p = Start-Process -FilePath "msiexec" -ArgumentList $list -Wait -PassThru
if($p.ExitCode -ne 0)
{
throw "Installation process returned error code: $($p.ExitCode)"
}
Depends. MSIs can be installed using WMI. For exes and other methods, you can use Start-Process and check the Process ExitCode.
msi's can also be installed using msiexec.exe, msu's can be installed using wusa.exe, both have a /quiet switch, /norestart and /forcerestart switches and a /log option for logging (specify the file name).
You can read more about the options if you call them with /?
Note: wusa fails silently when they fail, so you have to check the log file or eventlog to determine success.
I have implemented exactly what you are looking for at my current project. We need to automate deployment and instillation of n number of apps across multiple environments and datacenters. These scripts are slightly modified from the original version for simplicity sake since my complete code is reaching 1000 lines but the core functionality is intact. I hope this does what you are asking for.
This PS function pulls all the apps from the registry (what add/remove programs reads from) and then search's for the supplied app name and display version. In my code (PSM1) I run this function before I install to whether or not it is their and then afterword’s to verify that it got installed…. All this can be wrapped in one master function to manager flow control.
function Confirm-AppInstall{
param($AppName,$AppVersion)
$Apps = Get-ItemProperty Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*|?{$_.DisplayName -ne $Null}|?{$_.DisplayName -ne ""}
$Apps += Get-ItemProperty Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*|?{$_.DisplayName -ne $Null}|?{$_.DisplayName -ne ""}
$Installed = $Apps|?{$_.DisplayName -eq ""}|?{$_.DisplayVersion -eq ""}|select -First 1
if($Installed -ne $null){return $true}else{return $false}
}
This PS function will load a txt file that has the install commands prepopulated (one command per line). and run each line indivaduly and wait for the install to complete before moving on to the next.
function Install-Application{
param($InstallList = "C:\Install_Apps_CMDS.txt")
$list = gc -Path $InstallList
foreach ($Command in $list){
Write-Output ("[{0}]{1}" -f (Get-Date -Format G),$call)
#Make install process wait for exit before continuing.
$p = [diagnostics.process]::Start("powershell.exe","-NoProfile -NoLogo -Command $Command")
$p.WaitForExit()
Start-Sleep -Seconds 2
#Searches for the installer exe or msi that was directly opened by powershell and gets the process id.
$ProcessID = (gwmi -Query ("select ProcessId from Win32_Process WHERE ParentProcessID = {0} AND Name = '{1}'" -f $p.Id,$ParentProcessFile)|select ProcessId).ProcessId
#waits for the exe or msi to finish installing
while ( (Get-Process -Id $ProcessID -ea 0) -ne $null){
Start-Sleep -Seconds 2
$ElapsedTime = [int](New-TimeSpan -Start $P.StartTime -End (Get-Date)|select TotalSeconds).TotalSeconds
#install times out after 1000 seconds so it dosent just sit their forever this can be changed
if(2000 -lt $ElapsedTime){
Write-Output ('[{0}] The application "{1}" timed out during instilation and was forcfully exited after {2} seconds.' -f (Get-Date -Format G),$App.Name,(([int]$App.InstallTimeOut) * 60))
break
}
}
#clean up any old or hung install proccess that should not be running at this point.
Stop-Process -Name $ParentProcessName -ea 0 -Force
Stop-Process -Name msiexec -ea 0 -Force
}
}
The TXT file should be formatted as such... you will need to do you research on how to each app needs to be installed. a good resource is appdeploy.com
C:\Install.exe /q
C:\install.msi /qn TRANSFORMS='C:\transform.mst'
C:\install2.msi /qn /norestart
C:\install3.exe /quiet
Let me know if there are any errors I had to modify my existing code to remove the proprietary values and make this a little more simplistic. I am pulling my values from a custom XML answer sheet. But this code should work as I have supplied it.
If you would like for me to discuss more about my implementation let me know and i can make a more detailed explanation and also add more of the supporting functions that I have implemented.