Mapped drive monitoring using PowerShell 2.0 - powershell

I'm writing a PowerShell 2.0 script to monitor and reconnect network drives that should be mapped. It works...mostly. Once a drive is determined to be disconnected my logon script (ptt.vbs) is run and the drives are remapped. However, after that it continues to see that particular drive letter as disconnected even though it got remapped.
while($true) {
$disconnectedDrives = #()
$mappedDrives = 'J:', 'R:', 'S:', 'W:'
foreach ($drive in $mappedDrives) {
if (-Not (Test-Path $drive)) {
$disconnectedDrives += $drive
}
}
if ($disconnectedDrives) {
Write-Host "$disconnectedDrives not mapped."
Write-Eventlog -LogName 'Windows PowerShell' -Category 3 -source PowerShell -eventID 601 -EntryType Error -message "$disconnectedDrives OFFLINE and not available."
\\dc1\NETLOGON\ptt.vbs
}
Start-Sleep 1
}
Any ideas what I'm doing wrong?

To me the mix of VBS and Powershell is suspicious.
But at least a would take a differnt way. First create a central file whch includes the network drive configuration. For example "\dc1\NETLOGON\ptt.csv" can look like this ...
J;\\srv1\shareA
R;\\srv1\shareB
S;\\srv2\shareA
W;\\srv2\shareB
This is the script ...
$Local:LogicalDiskStatus = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Management.ManagementObject]'
$Local:NetDrvConf_Path = "\\dc1\NETLOGON\ptt.csv"
$Local:NetDrvConf_List = #()
$Local:NetDrvConf_DrvType = [UInt32]4
$Local:WshNetworkObj = New-Object -ComObject WScript.Network
while($true) {
# adding the logical disk status to a list indexed by DeviceID
$LogicalDiskStatus.Clear()
Get-WmiObject -Class "Win32_LogicalDisk" | ForEach-Object { $LogicalDiskStatus.Add((($_.DeviceID) -replace ":"), $_) }
if (!($NetDrvConf_List.Count)) {
Try {
$NetDrvConf_List.Clear()
$NetDrvConf_List = Import-Csv -Path $NetDrvConf_Path -Delimiter ";"
}
Catch [System.Exception] {
$Local:ErrorMsg = ("Cannot load network drive configuration file `"{0}`". Error message: {1}" -f $NetDrvConf_Path, ($_.Exception.Message))
Write-Host $ErrorMsg -ForegroundColor red
Write-Eventlog -LogName "Windows PowerShell" -Category 3 -Source PowerShell -EventId 601 -EntryType Error -Message $ErrorMsg
}
}
$NetDrvConf_List | ForEach-Object {
$Local:Current_DriveLetter = ($_.Drive).ToUpper()
$Local:Current_DriveID = ("{0}:" -f $Current_DriveLetter)
$Local:Current_UNCPath = $_.UNCPath
Write-Host ("Check configuration: {0} {1}" -f $Current_DriveID, $Current_UNCPath)
# drive in use?
if ( ($LogicalDiskStatus.ContainsKey($Current_DriveLetter)) ) {
# drive is a network drive?
if (!(($LogicalDiskStatus.$Current_DriveLetter.DriveType).Equals($NetDrvConf_DrvType))) {
$Local:ErrorMsg = ("Drive `"{0}`" is already in use, but not as a network drive! The current drive type is `"{1}`" not `"4`"." -f $Current_DriveID, ($LogicalDiskStatus.$Current_DriveLetter.DriveType) )
Write-Host $ErrorMsg -ForegroundColor red
Write-Eventlog -LogName "Windows PowerShell" -Category 3 -Source PowerShell -EventId 602 -EntryType Error -Message $ErrorMsg
# drive is NOT to the expected UNC path?
} elseif (!(($LogicalDiskStatus.$Current_DriveLetter.ProviderName).Equals($Current_UNCPath))) {
Try {
Write-Host ("Network drive `"{0}`" unexpectedly connected to `"{1}`". Trying to disconnect." -f $Current_DriveID, ($LogicalDiskStatus.$Current_DriveLetter.ProviderName)) -ForegroundColor yellow
$WshNetworkObj.RemoveNetworkDrive($Current_DriveID, $true, $true)
Write-Host ("=> successfully disconnected.") -ForegroundColor green
}
Catch [System.Exception] {
$Local:ErrorMsg = ("Error disconnecting `"{0}`". Connected to `"{1}`". Error message: {2}" -f $Current_DriveID, $Current_UNCPath, ($_.Exception.InnerException) )
Write-Host $ErrorMsg -ForegroundColor red
Write-Eventlog -LogName "Windows PowerShell" -Category 3 -Source PowerShell -EventId 603 -EntryType Error -Message $ErrorMsg
}
} else {
Write-Host "=> correct connected" -ForegroundColor green
}
}
# drive is unused?
if (!(((Get-PSProvider -PSProvider FileSystem).Drives.Name).Contains($Current_DriveLetter))) {
Try {
Write-Host ("Connecting network drive `"{0}`" to `"{1}`"." -f $Current_DriveID, $Current_UNCPath) -ForegroundColor yellow
$WshNetworkObj.MapNetworkDrive($Current_DriveID, $Current_UNCPath, $true)
Write-Host ("=> successfully connected.") -ForegroundColor green
}
Catch [System.Exception] {
$Local:ErrorMsg = ("Error connecting `"{0}`" to `"{1}`". Error message: {2}" -f $Current_UNCPath, $Current_DriveID, ($_.Exception.InnerException) )
Write-Host $ErrorMsg -ForegroundColor red
Write-Eventlog -LogName "Windows PowerShell" -Category 3 -Source PowerShell -EventId 604 -EntryType Error -Message $ErrorMsg
}
}
}
Start-Sleep -Seconds 1
}

Related

Using Powershell to backup remote computers event logs

I'm very new to PowerShell and my end goal is to backup event logs on remote servers to a fileshare on the network. I was able to get my script working locally on a single server, backing up the servers event logs to a folder. Now, I'm trying to run this script from "server A" and backup event logs on "Server B" and "Server C". I was hoping I can accomplish this via simply creating a new ps session for each server and copy pasting my code. This of course wasn't the case. I'm guessing I might have to go through and add -Computer parameters to somethings?
I got the foundational code from this website
Was hoping a PS guru could point out my failures, here's my code:
$Servers = 'ServerB'
$ArchiveServer = 'ServerA' #For Testing
foreach ($Server in $Servers){
Enter-PSSession -ComputerName $Server -Credential mycred
$RemoteArchive = "\\" + $ArchiveServer +"\c$\z-TestScript-lastname"
$LocalArchive = "C:\z-TestScript"
$ArchiveComp = "\$Server"
$ArchiveFolder = "\Archive-" + (Get-Date -Format "yyyy-MM-dd") + "test14" ### Added test to get past testpath check
$RemoteLogFolder = "\\" + $Server + "\c$\Windows\System32\winevt\Logs"
$LogFolder = "C:\Windows\System32\winevt\Logs\*"
$RemoteCompressedPath = -join("$RemoteArchive","$ArchiveComp","$ArchiveFolder",".zip")
$CompressedPath = -join("$ArchiveLoc","$ArchiveComp","$ArchiveFolder",".zip")
$RemoteArchivePath = -join("$RemoteArchive","$ArchiveComp","$ArchiveFolder")
$ArchivePath = -join("$ArchiveLoc","$ArchiveComp","$ArchiveFolder")
$winlogs = 'application', 'security', 'system', 'setup', 'hardwareevents', 'internet explorer', 'key management service', 'oalerts', 'Parameters', 'oneapp_IGCC', 'windows powershell'
# Checking paths
If (!(Test-Path $RemoteArchive)) {
Write-Host
Write-Host "Archive folder $RemoteArchive does not exist, aborting ..." -ForegroundColor Red
Exit
}
If ((Test-Path $RemoteArchivePath)) {
Write-Host
Write-Host "Archive path $RemoteArchivePath exists, aborting ..." -ForegroundColor Red
Exit
}
If (!(Test-Path $RemoteArchivePath)) {
Write-Host
Write-Host "Creating Archive folder $RemoteArchivePath ..." -ForegroundColor Red
New-Item -Path $RemoteArchivePath -type directory -Force
}
# For all newer event logs, archive.
foreach ($winlog in $winlogs) {
# Configure environment
$sysName = $Server
$eventName = "$winlog Event Log Monitoring"
# Add event source to log if necessary
If (-NOT ([System.Diagnostics.EventLog]::SourceExists($eventName))) {
New-EventLog -ComputerName $Server -LogName $winlog -Source $eventName
}
# Check the log
if ( $winlog -ne 'Setup'){
$Log = Get-WmiObject Win32_NTEventLogFile | Where-Object {$_.logfilename -eq "$winlog"}
# Archive the log
$ArchiveFile = $ArchivePath + "\$winlog-" + (Get-Date -Format "yyyy-MM-dd#HHmm") + ".evt"
$EventMessage = "The $winlog event log will now be backed up."
$Results = ($Log.BackupEventlog($ArchiveFile)).ReturnValue
If ($Results -eq 0) {
# Successful backup of the event log
$Results = ($Log.ClearEventlog()).ReturnValue
$EventMessage += "The $winlog event log was successfully archived to $ArchiveFile and cleared."
Write-Host $EventMessage
Write-EventLog -LogName $winlog -Source $eventName -EventId 11 -EntryType Information -Message $eventMessage -Category 0
}
Else {
$EventMessage += "The #winlog event log could not be archived to $ArchiveFile and was not cleared. Review and resolve security event log issues on $sysName ASAP!"
Write-Host $EventMessage
Write-EventLog -LogName $winlog -Source $eventName -EventId 11 -EntryType Error -Message $eventMessage -Category 0
}
# Close the log
$Log.Dispose()
}
# For older archives like setup, archive.
Else {
$Log = Get-winevent -Listlog Setup | select Logname, Logfilepath | ForEach-Object -Process {
$name = $_.Logname
$safename = $name.Replace("/","-")
$logpath = $_.Logfilepath
$path = $ArchivePath + "\Setup" + (Get-Date -Format "yyyy-MM-dd#HHmm") + ".evt"
wevtutil.exe EPL $safename $path
Write-Host "Copying Setup log to: $path"
}
}
}
#Copy any windows archived logs over, then delete
Write-Host "Copying Archived Logs..."
Get-ChildItem -Path $LogFolder -Include Archive* -Recurse | Copy-Item -Destination $ArchivePath
Get-ChildItem -Path $LogFolder -Include Archive* -Recurse | Remove-Item
#Compress the folder
Write-Host "Compressing $ArchivePath and moving to $CompressedPath"
Compress-Archive -Path $ArchivePath -Destination $CompressedPath
Exit-PSSession
}
Main errors I'm getting as it goes through the foreach loop for the winlogs, it seems to run it twice, once for remote pc and once for local.

Powershell not reading .txt lines

I cannot get PowerShell to run this .txt file, what am I doing wrong? I tried changing the name of the .txt and checked passed scripts and everything seems to be the same but I keep getting an error saying that the ".txt in invalid"
$POSName = "$PSScriptRoot\Bex.txt"
foreach ($POS in (Get-Content $POSName)) {
$Bex = Get-Service -ComputerName $POSName | Where-Object { $_.name -eq "BexServ" }
}
If ($Bex -eq $null) {
# Service does not exist
Write-Host " doesn't exist." -ForegroundColor Red
}
Else {
# Service does exist
Write-Host "The $($Bex.Name) service found." -ForegroundColor Green
If ($Bex.Status -eq "Running") {
# Stop Service
Set-Service -status stopped -ComputerName $POSName -name $Box.Name -ErrorAction Stop
Write-Host "The $($Bex.Name) successfully stopped." -ForegroundColor Green
}
else {
#service already stopped
If ($Bex.Status -eq "Stopped") {
Write-Host "The $($Bex.Name) service already Stopped." -ForegroundColor Green
}
}
}
As commented, you are using the wrong variable in the loop. The code is reading the text file just fine, it is Get-Service that cannot deal with a path to a file in the -ComputerName parameter.
Also, the placing of the if..else should be inside the loop, not after.
Try
$POSName = "$PSScriptRoot\Bex.txt"
foreach ($POS in (Get-Content $POSName)) {
$Bex = Get-Service -ComputerName $POS | Where-Object { $_.name -eq "BexServ" }
If (!$Bex) {
# Service does not exist
Write-Host " doesn't exist." -ForegroundColor Red
}
Else {
# Service does exist
Write-Host "The $($Bex.Name) service found." -ForegroundColor Green
If ($Bex.Status -eq "Running") {
# Stop Service
Set-Service -status stopped -ComputerName $POSName -name $Box.Name -ErrorAction Stop
Write-Host "The $($Bex.Name) successfully stopped." -ForegroundColor Green
}
else {
#service already stopped
If ($Bex.Status -eq "Stopped") {
Write-Host "The $($Bex.Name) service already Stopped." -ForegroundColor Green
}
}
}
}
It might also be a good idea to output the computername ($POS) in the Write-Host lines too

Powershell: Write to a single Logfile

I have written a small function that writes into a single logfile for each server this module is installed, but somehow a few cycles to write the line are lost, sometime I get the error file already in use. I have tested this function on different version of Powershell, PS5.1, PS6, PS7.1 x86 and x64 all with the same result.
if (!(Test-Path -ErrorAction Stop -Path (Join-path -Path $ScriptLogPath -ChildPath "$FQDN.log"))) {
New-Item -Path $ScriptLogPath -Name "$FQDN.log" -Force -ItemType File -ErrorAction Stop
}
$CreatedNew = $false
$Global:MTX = New-Object -TypeName System.Threading.Mutex($true, 'Global\LogFileMutex', [ref]$CreatedNew)
try {
if (-not $CreatedNew) {
$Global:MTX.WaitOne(10000) | Out-Null
}
}
catch [System.Threading.AbandonedMutexException] {
if (!(Test-Path -Path 'HKLM:\System\CurrentControlSet\services\eventlog\Application\LogWriter')) {
New-EventLog -LogName Application -Source LogWriter
}
Write-EventLog -LogName "Application" -Source "LogWriter" -EntryType Error -Message $_.Exception -EventId 1000
}
($Date + $InstanceId + $Severity + $ScriptLineNumber + $ScriptName + $Message) | Out-File -Append -FilePath (Join-path -Path $ScriptLogPath -ChildPath "$FQDN.log")
}
catch {
if (!(Test-Path -Path 'HKLM:\System\CurrentControlSet\services\eventlog\Application\LogWriter')) {
New-EventLog -LogName Application -Source LogWriter
}
Write-EventLog -LogName "Application" -Source "LogWriter" -EntryType Error -Message $_.Exception -EventId 1000
}
finally {
if ($Null -ne $Global:MTX) {
[void]$Global:MTX.ReleaseMutex()
[void]$Global:MTX.Dispose()
}
}
That is the block of code that will append the formatted string into the file.
So fare I have tried different approaches to handle the Mutex, made them globally and not. Used different approaches to append the string to the file, like Add-Content.
To try out the function I use this little script, it should write 150 lines, but end up with about 130 and no single error.
for ($num1 = 1 ; $num1 -le 5 ; $num1++) {
Start-Job {
for ($num = 1 ; $num -le 10 ; $num++) {
Write-Log -Severity 'INFO' -Message "Starting Job $num"
Start-Job -ScriptBlock {
try {
Start-Sleep -Milliseconds (Get-Random -Maximum 10)
Write-Log -Severity 'INFO' -Message "Starting"
Start-Sleep -Milliseconds (Get-Random -Maximum 10)
Write-Log -Severity 'INFO' -Message "DONE"
}
catch {
New-Item -Path "C:\Users\User\Desktop" -Name (Get-Date -Format "dd/MM/yyyy HH:mm:ss") -ItemType File
}
}
}
}
Since every server is writing to the same logfile, i dont think that you can override "error file already in use"
Iam thinking that maby you should think over the concept and run from a server where you are monitoring by invoke-commmand ?

If/Else Help in Powershell

Running an IF/Else statement and if "false" I want it to run a search for the current NVIDIA driver and tell me the current version then stop the script. Tried a couple different things to no avail. Currently it is continuing the script and running the "Get-WmiObject" out of order in a subsequent function.
Function Namespace_Check
{ Write-Host "Checking available namepace" -ForegroundColor Green
if ((Get-CimInstance -namespace "root\cimv2" -ClassName __NAMESPACE).Name -match 'NV'){return}
else { return (Get-WmiObject Win32_PnPSignedDriver| select devicename, driverversion | where {$_.devicename -like "*nvidia*"})}
Write-Host "If Stopped, install latest NVIDIA driver from SWE"
Write-Host "Complete" -ForegroundColor Green
}
Here is the whole script, if there needs to be other changes to the logic
Function Namespace_Check
{ Write-Host "Checking available namepace" -ForegroundColor Green
Write-Host "Complete" -ForegroundColor Green
if ((Get-CimInstance -namespace "root\cimv2" -ClassName __NAMESPACE).Name -match 'NV'){return}
else {
exit (Get-WmiObject Win32_PnPSignedDriver| select devicename, driverversion | where {$_.devicename -like "*nvidia*"})
Write-Warning "Install latest NVIDIA driver from SWE"
Write-Host
}
}
Function InstallSWE
{
Write-Host "Installing SWE Icon..." -ForegroundColor Green
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.FileName = "\\nt-iss-1\setools\xsetup.exe"
$ps.StartInfo.Arguments = " -Silent -NoPrompt -NoBanner"
Write-Host " Execute Software Express"
[Void]$ps.start()
$ps.WaitForExit()
Write-Host "Complete" -ForegroundColor Green
}
Function CheckNVIDIADriver
{
Write-Host "Checking NVIDIA Drivers..." -ForegroundColor Green
$productName = Get-WmiObject -namespace "root\cimv2\nv" -Class Gpu | Select -ExpandProperty productName
$driverVersion = Get-Wmiobject -namespace "root\cimv2\nv" -class System | Select -ExpandProperty verDisplayDriver | Select -ExpandProperty strValue
Write-Host " Product Name : $productName"
Write-Host " Video Driver Version: $driverVersion"
Write-Host "Complete" -ForegroundColor Green
}
Function SetNVIDIA3DGlobalPreset
{
Write-Host "Setting NVIDIA 3D Global Preset..." -ForegroundColor Green
$global3DPreset = "Dassault Systemes CATIA - compatible"
$profileManager = Get-WmiObject -namespace "root\cimv2\nv" -Class ProfileManager
"Change Global 3D Preset to $global3DPreset"
[void](Invoke-WmiMethod -Path $profileManager.__PATH -Name setCurrentProfile3D -ArgumentList $global3DPreset, $null)
Write-Host "Complete" -ForegroundColor Green
}
Function SetScreenSaver
{
Write-Host "Setting Screen Saver..." -ForegroundColor Green
$regkeypath = "HKCU:\Control Panel\Desktop"
$screensaver = "C:\Windows\SysWOW64\CORPOR~1.SCR"
Write-Host " Change Screen Saver to Corporate Screen Saver"
Set-ItemProperty -Path $regkeypath -Name "SCRNSAVE.EXE" -Value $screensaver
Write-Host "Complete" -ForegroundColor Green
}
Function ChangePowerSettings
{
Write-Host "Changing Power Settings..." -ForegroundColor Green
Write-Host " Disable Monitor Timeout"
powercfg -x -monitor-timeout-ac 10
powercfg -x -monitor-timeout-dc 10
Write-Host " Disable Disk Timeout"
powercfg -x -disk-timeout-ac 0
powercfg -x -disk-timeout-dc 0
Write-Host " Disable Standby Timeout"
powercfg -x -standby-timeout-ac 0
powercfg -x -standby-timeout-dc 0
Write-Host " Disable Hibernate and Hybrid Sleep"
powercfg -x -hibernate-timeout-ac 0
powercfg -x -hibernate-timeout-dc 0
powercfg -h off
Write-Host "Complete" -ForegroundColor Green
}
Function ChangeVirtualMemory
{
Write-Host "Changing Virtual Memory Settings..." -ForegroundColor Green
$mem = [Math]::Round((Get-WmiObject -Class Win32_ComputerSystem |Select -ExpandProperty TotalPhysicalMemory) / [Math]::pow(1024, 3))
$initialSize = 2048 * $mem
$maximumSize = 2 * $initialSize
Write-Host " Disable Automatic Managed Page File"
$System = Get-WmiObject Win32_ComputerSystem -EnableAllPrivileges
$System.AutomaticManagedPagefile = $False
[Void]$System.Put()
Write-Host " Set Page File Size (Initial Size: $initialSize MB / Maximum Size: $maximumSize MB)"
$PageFile = Get-WmiObject -class Win32_PageFileSetting
$PageFile.InitialSize = $initialSize
$PageFile.MaximumSize = $maximumSize
[Void]$PageFile.Put()
Write-Host "Complete" -ForegroundColor Green
}
Function EnableRDP
{
Write-Host "Enabling Remote Desktop and User..." -ForegroundColor Green
$computername = $(gc env:computername)
$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -Namespace root\CIMV2\TerminalServices
Write-Host " Enable Remote Desktop"
$result = $RDP.SetAllowTsConnections(1, 1)
Try
{
$user ="CAxILABRemoteUsers"
$domain ='NW'
$objUser = [ADSI]("WinNT://$domain/$user")
$objGroup = [ADSI]("WinNT://$computername/Remote Desktop Users")
Write-Host " Add Remote Desktop User $domain\$user"
$objGroup.PSBase.Invoke('Add',$objUser.PSBase.Path)
}
Catch
{
}
Write-Host "Complete" -ForegroundColor Green
}
Function CheckAdministrator
{
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
# Filename: CAxILab_Final_Config.ps1
$pshost = get-host
$pswindow = $pshost.ui.rawui
$newsize = $pswindow.buffersize
$newsize.height = 999
$pswindow.buffersize = $newsize
$newsize = $pswindow.windowsize
#$newsize.height = 60
#$pswindow.windowsize = $newsize
$host.ui.RawUI.ForegroundColor = "White"
$isAdmin = CheckAdministrator
if ($isAdmin)
{
Namespace_Check
Write-Host
InstallSWE
Write-Host
CheckNVIDIADriver
Write-Host
SetNVIDIA3DGlobalPreset
Write-Host
SetScreenSaver
Write-Host
ChangePowerSettings
Write-Host
ChangeVirtualMemory
Write-Host
EnableRDP
Write-Host
}
else
{
Write-Warning "Administrator rights is required to run this script"
Write-Host
}
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
# end of script
If you want the script stopped completely when the namespace is not found, change the function Namespace_Check into something like this:
Function Namespace_Check {
Write-Host "Checking available namepace" -ForegroundColor Green
if (!((Get-CimInstance -namespace "root\cimv2" -ClassName __NAMESPACE).Name -match 'NV')) {
# if we got here, the namespace is not found
# output the current version, then stop the script
$driver = Get-WmiObject Win32_PnPSignedDriver|
Select-Object devicename, driverversion |
Where-Object {$_.devicename -like "*nvidia*"}
Write-Host "If Stopped, install latest NVIDIA driver from SWE"
if ($driver) {
Write-Host "Current driver(s)" -ForegroundColor Yellow
$driver | Format-Table -AutoSize
}
else {
Write-Warning "No nvidia drivers found"
}
Write-Host "Script Complete" -ForegroundColor Green
# stop the script
exit
}
}
In your version you are trying to write to console after you have exited the function using a return statement. These lines will therefore never be executed.
Edit
Alternatively, you can have the function Namespace_Check return a value of $true of $false like this:
Function Namespace_Check {
Write-Host "Checking available namepace" -ForegroundColor Green
if (!((Get-CimInstance -namespace "root\cimv2" -ClassName __NAMESPACE).Name -match 'NV')) {
# if we got here, the namespace is not found
# output the current version, then stop the script
$driver = Get-WmiObject Win32_PnPSignedDriver|
Select-Object devicename, driverversion |
Where-Object {$_.devicename -like "*nvidia*"}
Write-Host "If Stopped, install latest NVIDIA driver from SWE"
if ($driver) {
Write-Host "Current driver(s)" -ForegroundColor Yellow
$driver | Format-Table -AutoSize
}
else {
Write-Warning "No nvidia drivers found"
}
Write-Host "Script Complete" -ForegroundColor Green
return $false
}
return $true
}
and in the main part of your script do
if ($isAdmin) {
if (Namespace_Check) {
Write-Host
InstallSWE
Write-Host
CheckNVIDIADriver
Write-Host
SetNVIDIA3DGlobalPreset
Write-Host
SetScreenSaver
Write-Host
ChangePowerSettings
Write-Host
ChangeVirtualMemory
Write-Host
EnableRDP
Write-Host
}
}
else {
Write-Warning "Administrator rights is required to run this script"
Write-Host
}
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
# end of script
This will make the script end gracefully instead of when using the exit statement.

how to automate Wusa with remoting and overcoming the lack of wait cmd

Scenario: Taking a list of kb files and executing them remotely with WUSA install of target machines across network.
The flow is like this:
enter a powershell session W/ target computer
for each loop $KB in $List
wusa.exe $kb
wait 'til installed
back to the wusa.exe for next device in $list
Code:
# SET UP ERROR-HANDLING FOR THE PS SESSION
$ErrorActionPreference = "continue"
# DECLARE VARIABLE THAT CONTAINS LIST OF PATCHES AVAILABLE FOR INSTALL
$PatchList = (Get-ChildItem -Path C:\WinPatch -recurse | Where-Object {$_.Extension -eq '.msu'})
# ESTABLISH LOOP TO ITERATE THRU PATCHES
foreach ($Patch in $PatchList)
{
Try
{
Write-Host ("`n Preparing to install: " + $Patch) -ForegroundColor Yellow
Write-Host ("`n Installing...") -ForegroundColor Magenta
$SB = {
$arglist = "$Patch", "/quiet", "/norestart"
Start-Process -FilePath "C:\windows\system32\wusa.exe" -ArgumentList $arglist -Wait}
Invoke-Command -ScriptBlock $SB
Write-Host "`n Installation complete`n" -ForegroundColor Green
}
Catch
{
[System.Exception]
Write-Host "Installation failed with Error -- $Error()" -ForegroundColor Red
$Error.Clear()
}
}
# RESTART OPTIONS
$Ans1 = Read-Host "`n Would you like to restart this computer now (Type Y for yes or N for no)"
if ($Ans1 -eq 'Y' -or $Ans1 -eq 'y')
{
Remove-Item -Path C:\WinPatch\*.msu -Force
Write-Host "`n This computer will restart in 5 seconds..." -ForegroundColor Yellow
Start-Sleep -Seconds 5
Restart-Computer -Force
}
else
{
# TEST COMPUTER FOR PS VERSION
$Tester = test-wsman -computername localhost | Select-Object -Property ProductVersion
if ($Tester.ProductVersion.EndsWith("1.0"))
{
Write-Host "`n This computer has PS v1.0 installed and you will have to open Task Scheduler to schedule restart" -ForegroundColor Red
Read-Host "`n Press ENTER to continue..."
}
elseif ($Tester.ProductVersion.EndsWith("2.0"))
{
Write-Host "`n This computer has PS v2.0 installed and you will have to open Task Scheduler to schedule restart" -ForegroundColor Red
Read-Host "`n Press ENTER to continue..."
}
else
{
# SCHEDULE RESTART
Import-Module PSScheduledJob
$RST = Read-Host -Prompt "`n Enter date/time to restart computer...format is --> mm/dd/yyyy hh:mmAM/PM"
$Ans2 = Read-Host -Prompt "`n You entered: $RST... if this is correct, enter Y for yes"
if ($Ans2 -eq 'Y' -or $Ans2 -eq 'y')
{
$Nomen = Read-Host -Prompt "`n Enter name for scheduled restart "
$Trig = New-JobTrigger -Once -At $RST
Register-ScheduledJob -Name $Nomen -Trigger $Trig -ScriptBlock { Restart-Computer -force } -RunAs32
}
else
{
Write-Host "`n Please restart the script and try again" -ForegroundColor Red
}
}
}
Break
PsExec.exe -u $domain\$username -p $password -h -s -accepteula \\$computer wusa.exe /quiet 'C:\Program Files\WindowsPowershell\Modules\windows10.0-kb5005112-x64.msu' /wait /forcerestart
This works for me. If you wrap this in an Invoke-Command block you can run this remotely. :-)
Thanks,