Update Windows security remotely using powershell scheduled task - powershell

I am trying to Install windows security patches on a remote machine using powershell remoting.
This is the function i am using to Update windows
<#
.SYNOPSIS
This functiion will automatically install all avaialable windows updates on a device and will automatically reboot if needed, after reboot, windows updates will continue to run until no more updates are available.
#>
function Install-WindowsUpdates
{
Install-Module -Name PSWindowsUpdate -RequiredVersion 2.1.0.1 -Force
Import-Module PSWindowsUpdate -Force
Get-WindowsUpdate -install -acceptall
}
When i run this function on a local host, the function is successful in installing windows security patches. I have the below script to do the same remotely:
param(
[Parameter(Mandatory = $true)]
[string] $IPaddress
)
try
{
$secpasswd = ConvertTo-SecureString "Pass#12345678" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("Admin02", $secpasswd)
#Create a Session.
$Session = New-PSSession -ComputerName $IPaddress -Credential $cred
cd C:\Users\Admin01\Documents
. .\Install-WindowsUpdates.ps1
Invoke-Command -Session $Session -ScriptBlock ${function:Install-WindowsUpdates}
return $true
}
catch
{
return $false
}
When i run this script i am getting the below error:
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
+ CategoryInfo : NotSpecified: (:) [Get-WindowsUpdate], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,PSWindowsUpdate.GetWindowsUpdate
+ PSComputerName : 10.0.0.7
I have setup both the loaclhost and remote machine for remoting and able to execute other scripts remotely. Also have enabled WMI on the remote machine.
What other settings i have to do?
Using Scheduled Task:
I am using the following script to start a scheduled task:
param(
[parameter(Mandatory = $true)]
[string]$IPaddress
)
$PSModulePath = $env:PSModulePath
$SplittedModulePath = $PSModulePath.Split(";")
$ModulePath = $SplittedModulePath[0]
$secpasswd = ConvertTo-SecureString "Pass#12345678" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("Admin02", $secpasswd)
#Create a Session. Replace host name with the host name of the remote machine.
$Session = New-PSSession -ComputerName $IPaddress -Credential $cred
$User= "Admin02"
$Action= New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "$env:ALLUSERSPROFILE\Install-WindowsUpdate.ps1"
$Trigger= New-ScheduledTaskTrigger -At 5:05am -Once
Invoke-Command -Session $Session -ScriptBlock { Register-ScheduledTask -TaskName "Install-Updates" -User $Using:User -Action $Using:Action -Trigger $Using:Trigger -RunLevel Highest –Force }
I have copied the below script on the target machine at the path $env:ALLUSERSPROFILE
<#
.SYNOPSIS
This functiion will automatically install all avaialable windows updates on a device and will automatically reboot if needed, after reboot, windows updates will continue to run until no more updates are available.
.PARAMETER computer
Use the Computer parameter to specify the Computer to remotely install windows updates on.
#>
Install-Module -Name PSWindowsUpdate -RequiredVersion 2.1.0.1 -Force
Import-Module PSWindowsUpdate -Force
Get-WindowsUpdate -install -acceptall
After i schedule the task nothing is happening.What i am doing wrong?

Yea, I fought this for weeks and finally have a good solution. The solution is actually built right into the PSWindowsUpdate module. The built in solution does use a windows Task, but it launches right away, and its actually helpful in tracking its completion progress, and it keeps the integration secure. The issue I have found is that PSWindowsUpdate has poor documentation. The following code worked for me:
Invoke-WUJob -ComputerName $svr -Script {ipmo PSWindowsUpdate; Get-WUInstall -AcceptAll -AutoReboot -Install | Out-File C:\PSWindowsUpdate.log } -Confirm:$false -Verbose -RunNow
There is a lot of scattered information on this topic, so please do your reading. PSWindowsUpdate is by far the best library for this job, and although its been a long process for me, I believe the above solution will work for everyone.
Please remember, the computer you are running the above scrip from needs to trust the computer you are trying to update, you can run this script to trust the computer:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value <ComputerName>
NOTE: Wildcards can be used in computer name
I also wanted to give you some information that greatly helped me:
Get-WindowsUpdate: This is the main cmdlet of the module. It lists, downloads, installs or hides a list of updates meeting predefined requisites and sets the rules of the restarts when installing the updates.
Remove-WindowsUpdate: Uninstalls an update
Add-WUServiceManage: Registers a new Windows Update API Service Manager
Get-WUHistory: Shows a list of installed updates
Get-WUSettings: Gets Windows Update client settings
Get-WUInstallerStatus: Gets Windows Update Installer Status, whether it is busy or not
Enable-WURemoting: Enables firewall rules for PSWindowsUpdate remoting
Invoke-WUJob: Invokes PSWindowsUpdate actions remotely
Like for all PowerShell cmdlets, different usage examples can be shown for each command typing Get-Help “command” -examples.
PSWindowsUpdate main parameters
As shown in the previous section, the PSWindowsUpdate module includes different predefined aliases to ease patching processes. However, main parameters for the Get-WindowsUpdate cmdlet will be listed and explained below:
Filtering updates:
AcceptAll: Downloads or installs all available updates
KBArticleID: Finds updates that contain a KBArticleID (or sets of KBArticleIDs)
UpdateID: Specifies updates with a specific UUID (or sets of UUIDs)
Category: Specifies updates that contain a specified category name, such as ‘Updates,’ ‘Security Updates’ or ‘Critical Updates’
Title: Finds updates that match part of title
Severity: Finds updates that match part of severity, such as ‘Important,’ ‘Critical’ or ‘Moderate’
UpdateType: Finds updates with a specific type, such as ‘Driver’ and ‘Software.’ Default value contains all updates
Actions and targets:
Download: downloads approved updates but does not install them
Install: installs approved updates
Hide: hides specified updates to prevent them to being installed
ScheduleJob: specifies date when job will start
SendReport: sends a report from the installation process
ComputerName: specifies target server or computer
Client restart behavior:
AutoReboot: automatically reboots system if required
IgnoreReboot: suppresses automatic restarts
ScheduleReboot: specifies the date when the system will be rebooted.
#How to avoid accidental installs#
Windows updates and patches improve the features and stability of the system. However, some updates can mess up your system and cause instability, especially automatic updates for legacy software such as graphic card drivers. To avoid automatic updates and accidental installs for such applications, you can pause Windows updates.
Alternatively, you can hide the specific updates for those features you don’t want to get updated. When you hide the updates, Windows can no longer download and install such updates. Before you can hide the update, you need to find out its details, including its knowledge base (KB) number and title. Type the cmdlet below to list all the available updates on your system:
Get-WUList
To hide a specific update using the KB number, use your mouse to copy that KB number. Next, type the command below:
Hide-WUUpdate -KBArticleID KB_Number
Highlight the “KB_Number” and click paste to replace that part with the actual KB number.
When prompted to confirm the action, type A, and hit the Enter key. If the command succeeds, the “Get-WUList” lists all the available updates, with hidden updates appearing with the symbol “H” under their status.
The KB number for the update may not be available for some updates. In this case, you can use the title to hide the update. To do this, list all the available updates via the cmdlet below:
Get-WUList
Next, use your mouse to copy the update title. Ensure it is distinct from other update titles. Now, type below command below to hide the update:
Hide-WUUpdate -Title “Update_Title”
Don’t forget to paste the actual update title in the “Update Title” section.
When prompted to confirm the action, type A, and hit the Enter key. If the command succeeds, the “Get-WUList” lists all the available updates. However, the status of hidden updates appears with the symbol “H” underneath them.
How to determine errors
It is of crucial importance to have as much information as possible about Windows Updates installation processes in order to be able to fix erroneous deployments. The Get-WindowsUpdate cmdlet and the rest of cmdlets available in the module, provide a very detailed log level when managing updates, including status, KB ID, Size or Title.
Centralizing all of the computer logs and analyzing them searching for errors, administrators will always be able to know the patch level of their Windows computers and servers.
The above passages came from this site!

This seems to be not possible by design:
Source 1
Source 2
Source 3
It is impossible for remotely connected users to download stuff from the internet it appears.

Speaking about windows update, you have many options like:
Connection using psexec tool then run wuauclt /detectnow /updatenow
If you are using windows 10 /server 2016 , the tools was replaced with USOClient.exe which is more effective.

Related

Can't enter remote powershell 7.1 session

Been able to do it against Microsoft.PowerShell (5.1), but today I hit a known issue on 5.1 with remote Copy-Item so I installed PowerShell 7 on the remote server (checking "Enable Remoting" in the installer) and am trying to get it working.
$securePassword = ConvertTo-SecureString -AsPlainText -Force -String $Password
$credential = New-Object -TypeName system.management.automation.pscredential -ArgumentList $Username, $securePassword
$session = New-PSSession $targetMachineHostName -Credential $credential -ConfigurationName "Microsoft.PowerShell"
Enter-PSSession $session
Above works. But if I change ConfigurationName to "PowerShell.7.1.0" I get:
[myserver.com.au] Connecting to remote server myserver.com.au failed with
| the following error message : <f:WSManFault
| xmlns:f="http://schemas.microsoft.com/wbem/wsman/1/wsmanfault" Code="2689860592"
| Machine="myserver.com.au"><f:Message><f:ProviderFault provider="PowerShell.7.1.0"
| path="C:\Windows\system32\PowerShell\7.1.0\pwrshplugin.dll"></f:ProviderFault></f:Message></f:WSManFault> For more information, see the about_Remote_Troubleshooting Help topic.
On the remote server I've run enable ps remoting in a 7.1 powershell so if I run Get-PSSessionConfiguration it returns a bunch of configurations, including the following:
Name : PowerShell.7.1.0
PSVersion : 7.1
StartupScript :
RunAsUser :
Permission : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote
Management Users AccessAllowed
The dll the error refers to exists on the machine.
The user credentials I'm using are for a Local User on the remote machine that isn't an Administrator, but belongs to the Remote Management Users group.
Also worth noting from the remote machine itself (as a different Adminstrator local account, I can start a session to localhost).
After making the user an Administrator I was able to connect, but I'd gone to great lengths earlier to make non-Adminstrator possible on 5.1.
Out of interest, I tried giving the user Full Control to C:\Windows\system32\PowerShell\7.1.0 and then I could connect...
Still would love to know what's going on though and whether I'm doing the right thing or minimum permissions required.
It seems like the minimum security permissions to the folder are:
Read & Execute
List folder contents
Read
Write
Write is bizarre, but without it I get that error. I've assigned those permissions to the "Remote Management Users" group.
Docs here touch a little bit on v5.1 vs v7, and then link to here mentioning an install script so maybe something has fallen through the cracks.
I was getting the same error. I installed PowerShell 7 from Microsoft Store and then ran Enable-PSRemoting. I got this error so I uninstalled it and reinstalled it from WinGet which uses the MSI. That didn't work either. I tried running Enable-PSRemoting again, but nothing changed.
I ran Install-PowerShellRemoting.ps1 and it gave me two errors about things already existing and did not fix the problem. However, I was able to resolve the problem by doing the following:
Delete the PowerShell 7 plugins: Remove-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN\Plugin\PowerShell.7','HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN\Plugin\PowerShell.7.1.1'.
Run Install-PowerShellRemoting.ps1 again.
I'm not sure what the difference was, but deleting and allowing the script to generate it again fixed it for me.

Why don't the applications run by Powershell appear on remote desktop but appear in task manager? [duplicate]

I've created a pssession on a remote computer and entered that possession. From within that session I use start-process to start notepad. I can confirm that notepad is running with the get-process command, and also with taskmgr in the remote computer. However, the GUI side of the process isn't showing. This is the sequence I've been using:
$server = New-PSSession -ComputerName myserver -Credential mycreds
Enter-PSSession $server
[$server]: PS C:\>Start-Process notepad -Wait -WindowStyle Maximized
The process is running, but while RDP'd to the box, notepad does not open. If I open notepad from the server, a new notepad process begins. I also tried by using the verb parameter like this:
[$server]: PS C:\>Start-Process notepad -Wait -WindowStyle Maximized -Verb Open
Same result tho... Process starts, but no notepad shows. I've tried this while remoted into the box (but issued from my local host) as well as before remoting into the server.
That is because your powershell session on the remote machine does not go to any visible desktop, but to an invisible system desktop. The receiving end of your powershell remote session is a Windows service. The process is started, but nor you nor anyone else can ever see it.
And if you think about it, since multiple users could RDP to the same machine, there is really no reason to assume a remote powershell session would end up showing on any of the users desktops. Actually, in almost all cases you wouldn't want it anyway.
psexec with the -i parameter is able to do what you want, but you have to specify which of the sessions (users) you want it to show up in.
I know this is old, but I came across it looking for the solution myself so I wanted to update it for future poor souls.
A native workaround for this problem is to use a scheduled task. That will use the active session
function Start-Process-Active
{
param
(
[System.Management.Automation.Runspaces.PSSession]$Session,
[string]$Executable,
[string]$Argument,
[string]$WorkingDirectory,
[string]$UserID
)
if (($Session -eq $null) -or ($Session.Availability -ne [System.Management.Automation.Runspaces.RunspaceAvailability]::Available))
{
$Session.Availability
throw [System.Exception] "Session is not availabile"
}
Invoke-Command -Session $Session -ArgumentList $Executable,$Argument,$WorkingDirectory,$UserID -ScriptBlock {
param($Executable, $Argument, $WorkingDirectory, $UserID)
$action = New-ScheduledTaskAction -Execute $Executable -Argument $Argument -WorkingDirectory $WorkingDirectory
$principal = New-ScheduledTaskPrincipal -userid $UserID
$task = New-ScheduledTask -Action $action -Principal $principal
$taskname = "_StartProcessActiveTask"
try
{
$registeredTask = Get-ScheduledTask $taskname -ErrorAction SilentlyContinue
}
catch
{
$registeredTask = $null
}
if ($registeredTask)
{
Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false
}
$registeredTask = Register-ScheduledTask $taskname -InputObject $task
Start-ScheduledTask -InputObject $registeredTask
Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false
}
}
When you use New-PSSession and then RDP into that same computer, you're actually using two separate and distinct user login sessions. Therefore, the Notepad.exe process you started in the PSSession isn't visible to your RDP session (except as another running process via Task Manager or get-process).
Once you've RDP'd into the server (after doing what you wrote in your post), start another Notepad instance from there. Then drop to PowerShell & run this: get-process -name notepad |select name,processid
Note that there are two instances, each in a different session.
Now open up Task Manager and look at the user sessions. Your RDP session will probably be listed as session 1.
Now quit Notepad and run get-process again. You'll see one instance, but for session 0. That's the one you created in your remote PSSession.
There are only 2 workarounds that I know of that can make this happen.
Create a task schedule as the logged in user, with no trigger and trigger it manually.
Create a service that starts the process with a duplicated token of the logged in user.
For the task schedule way I will say that new-scheduledtask is only available in Windows 8+. For windows 7 you need to connect to the Schedule Service to create the task like this (this example also starts the task at logon);
$sched = new-object -ComObject("Schedule.Service")
$sched.connect()
$schedpath = $sched.getFolder("\")
$domain = "myDomain"
$user="myuser"
$domuser= "${domain}\${user}"
$task = $sched.newTask(0) # 0 - reserved for future use
$task.RegistrationInfo.Description = "Start My Application"
$task.Settings.DisallowStartIfOnBatteries=$false
$task.Settings.ExecutionTimeLimit="PT0S" # there's no limit
$task.settings.priority=0 # highest
$task.Settings.IdleSettings.StopOnIdleEnd=$false
$task.settings.StopIfGoingOnBatteries=$false
$trigger=$task.Triggers.create(9) # 9 - at logon
$trigger.userid="$domuser" # at logon
$action=$task.actions.create(0) # 0 - execute a command
$action.path="C:\windows\system32\cmd.exe"
$action.arguments='/c "c:\program files\vendor\product\executable.exe"'
$action.WorkingDirectory="c:\program files\vendor\product\"
$task.principal.Id="Author"
$task.principal.UserId="$domuser"
$task.principal.LogonType=3 # 3 - run only when logged on
$task.principal.runlevel=1 # with elevated privs
# 6 - TASK_CREATE_OR_UPDATE
$schedpath.RegisterTaskDefinition("MyApplication",$viztask,6,$null,$null,$null)
Creating a service is way more complicated, so I'll only outline the calls needed to make it happen. The easy way is to use the invoke-asservice script on powershell gallery: https://www.powershellgallery.com/packages/InvokeAsSystem/1.0.0.0/Content/Invoke-AsService.ps1
Use WTSOpenServer and WTSEnumerateSessions to get the list of sessions on the machine. You also need to use WTSQuerySessionInformation on each session to get additional information like username. Remember to free your resources using WTSFreeMemory and WTSCloseServer You'll end up with some data which looks like this (this is from the qwinsta command);
SESSIONNAME USERNAME ID STATE
services 0 Disc
>rdp-tcp#2 mheath 1 Active
console 2 Conn
rdp-tcp 65536 Listen
Here's an SO post about getting this data; How do you retrieve a list of logged-in/connected users in .NET?
This is where you implement your logic to determine which session to target, do you want to display it on the Active desktop regardless of how it's being presented, over RDP or on the local console? And also what will you do if there is no one logged on? (I've setup auto logon and call a lock desktop command at logon so that a logged in user is available.)
You need to find the process id of a process that is running on the desktop as that user. You could go for explorer, but your machine might be Server Core, which explorer isn't running by default. Also not a good idea to target winlogon because it's running as system, or dwm as it's running as an unprivileged user.
The following commands need to run in a service as they require privileges that only system services have. Use OpenProcess to get the process handle, use OpenProcessToken to get the security token of the process, duplicate the token using DuplicateTokenEx then call ``CreateProcessAsUser``` and finally Close your handles.
The second half of this code is implemented in invoke-asservice powershell script.
You can also use the sysinternals tool psexec, I didn't list it as a 3rd way because it just automates the process of creating a service.

Continue powershell script execution even if a reboot is encountered

All,
I am trying to write a script that updates a program on a remote machine, however the upgrade requires one or maybe two reboots. When i run the powershell script, it triggers the upgrade and the machine is rebooted once, post reboot the upgrade doesn't resume unless any user account logs in to the machine; post a user login the upgrade automatically resumes and the upgrade process triggers another reboot post which the upgrade is complete.
Is there a way to achieve this? Below is what i am trying.
Invoke-Command -ComputerName $name -ScriptBlock { Unblock-File 'C:\temp\Install\VDAServerSetup_1912.exe'; Start-Process -FilePath C:\temp\Install\VDAServerSetup_1912.exe -ArgumentList '/components VDA', /disableexperiencemetrics, /enable_hdx_ports, /enable_hdx_udp_ports, /enable_real_time_transport, /enable_remote_assistance, '/includeadditional "Citrix Personalization for App-V - VDA"','/exclude "Personal vDisk","Machine Identity Service"', '/includeadditional "Citrix Personalization for App-V - VDA"', '/logpath "c:\becnet\xenapp"', /masterimage, /quiet, /virtualmachine, /disableexperiencemetrics, /optimize, /virtualmachine -wait}
I removed the copy code as that part works fine. The problem i face is that once the above code is triggered on the remote machine, the machine reboots as a part of the upgrade, but once the machine is back up, the upgrade gets stuck unless someone logs on to the machine. Is there a way this can be achieved unattended?
As pointed by Larry below, i had success with the Autologon enabled, however i would like to use that method as the last resort, Is there any other way this can be achieved?
You haven't quoted arguments properly. And the c$ is a typo. Here's the fixed code:
Copy-Item -Path $Source_vda_path -Destination "\\$($name)\C:\$($temp)\Install\VDAServerSetup_1912.exe" -Force
Invoke-Command -ComputerName $name -ScriptBlock "{ & Unblock-File 'C:\temp\Install\VDAServerSetup_1912.exe'; Start-Process -FilePath 'C:\temp\Install\VDAServerSetup_1912.exe' -ArgumentList '/components VDA', '/disableexperiencemetrics', '/enable_hdx_ports', '/enable_hdx_udp_ports', '/enable_real_time_transport', '/enable_remote_assistance', '/includeadditional "Citrix Personalization for App-V - VDA"','/exclude "Personal vDisk","Machine Identity Service"', '/includeadditional "Citrix Personalization for App-V - VDA"', '/logpath "c:\becnet\xenapp"', '/masterimage', '/quiet', '/virtualmachine', '/disableexperiencemetrics', '/optimize', '/virtualmachine', -wait}"

Powershell - Copying File to Remote Host and Executing Install exe using WMI

EDITED: Here is my code now. The install file does copy to the remote host. However, the WMI portion does not install the .exe file, and no errors are returned. Perhaps this is a syntax error with WMI? Is there a way to just run the installer silently with PsExec? Thanks again for all the help sorry for the confusion:
#declare params
param (
[string]$finalCountdownPath = "",
[string]$slashes = "\\",
[string]$pathOnRemoteHost = "c:\temp\",
[string]$targetJavaComputer = "",
[string]$compname = "",
[string]$tempPathTarget = "\C$\temp\"
)
# user enters target host/computer
$targetJavaComputer = Read-Host "Enter the name of the computer on which you wish to install Java:"
[string]$compname = $slashes + $targetJavaComputer
[string]$finalCountdownPath = $compname + $tempPathTarget
#[string]$tempPathTarget2 =
#[string]$finalCountdownPath2 = $compname + $
# say copy install media to remote host
echo "Copying install file and running installer silently please wait..."
# create temp dir if does not exist, if exist copy install media
# if does not exist create dir, copy dummy file, copy install media
# either case will execute install of .exe via WMII
#[string]$finalCountdownPath = $compname + $tempPathTarget;
if ((Test-Path -Path $finalCountdownPath) )
{
copy c:\hdatools\java\jre-7u60-windows-i586.exe $finalCountdownPath
([WMICLASS]"\\$targetJavaComputer\ROOT\CIMV2:win32_process").Create("cmd.exe /c c:\temp\java\jre-7u60-windows-i586.exe /s /v`" /qn")
}
else {
New-Item -Path $finalCountdownPath -type directory -Force
copy c:\hdatools\dummy.txt $finalCountdownPath
copy "c:\hdatools\java\jre-7u60-windows-i586.exe" $finalCountdownPath
([WMICLASS]"\\$targetJavaComputer\ROOT\CIMV2:win32_process").Create("cmd.exe /c c:\temp\java\jre-7u60-windows-i586.exe /s /v`" /qn")
}
I was trying to get $Job = Invoke-Command -Session $Session -Scriptblock $Script to allow me to copy files on a different server, because I needed to off load it from the server it was running from. I was using the PowerShell Copy-Item to do it. But the running PowerShell script waits until the file is done copying to return.
I want it to take as little resources as possible on the server that the powershell is running to spawn off the process on another server to copy the file. I tried to user various other schemes out there, but they didn't work or the way I needed them to work. (Seemed kind of kludgey or too complex to me.) Maybe some of them could have worked? But I found a solution that I like that works best for me, which is pretty easy. (Except for some of the back end configuration that may be needed if it is is not already setup.)
Background:
I am running a SQLServer Job which invokes Powershell to run a script which backups databases, copies backup files, and deletes older backup files, with parameters passed into it. Our server is configured to allow PowerShell to run and under the pre-setup User account with SQL Server Admin and dbo privileges in an Active Directory account to allow it to see various places on our Network as well.
But we don't want it to take the resources away from the main server. The PowerShell script that was to be run would backup the database Log file and then use the another server to asynchronously copy the file itself and not make the SQL Server Job/PowerShell wait for it. We wanted it to happen right after the backup.
Here is my new way, using WMI, using Windows Integrate Security:
$ComputerName = "kithhelpdesk"
([Wmiclass]'Win32_Process').GetMethodParameters('Create')
Invoke-WmiMethod -ComputerName RemoteServerToRunOn -Path win32_process -Name create -ArgumentList 'powershell.exe -Command "Copy-Item -Path \\YourShareSource\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak -Destination \\YourShareDestination\YourDatabase_2018-08-07_11-45.log.bak"'
Here is my new way using passed in Credentials, and building arg list variable:
$Username = "YouDomain\YourDomainUser"
$Password = "P#ssw0rd27"
$ComputerName = "RemoteServerToRunOn"
$FromFile = "\\YourShareSource\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak"
$ToFile = "\\YourShareDestination\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak"
$ArgumentList = 'powershell.exe -Command "Copy-Item -Path ' + $FromFile + ' -Destination ' + $ToFile + '"'
$SecurePassWord = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $Username, $SecurePassWord
([Wmiclass]'Win32_Process').GetMethodParameters('Create')
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList $ArgumentList -Credential $Cred
We think that this above one is the preferred one to use.
You can also run a specific powershell that will do what you want it to do (even passing in parameters to it):
Invoke-WmiMethod -ComputerName RemoteServerToRunOn -Path win32_process -Name create -ArgumentList 'powershell.exe -file "C:\PS\Test1.ps1"'
This example could be changed to pass in parameters to the Test1.ps1 PowerShell script to make it more flexible and reusable. And you may also want to pass in a Credential like we used in a previous example above.
Help configuring WMI:
I got the main gist of this working from: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/invoke-wmimethod?view=powershell-5.1
But it may have also needed WMI configuration using:
https://helpcenter.gsx.com/hc/en-us/articles/202447926-How-to-Configure-Windows-Remote-PowerShell-Access-for-Non-Privileged-User-Accounts?flash_digest=bec1f6a29327161f08e1f2db77e64856b433cb5a
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enable-psremoting?view=powershell-5.1
Powershell New-PSSession Access Denied - Administrator Account
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/invoke-wmimethod?view=powershell-5.1 (I used to get how to call Invoke-WmiMethod).
https://learn.microsoft.com/en-us/powershell/scripting/core-powershell/console/powershell.exe-command-line-help?view=powershell-6 (I used to get syntax of command line)
I didn't use this one, but could have: How to execute a command in a remote computer?
I don't know for sure if all of the steps in the web articles above are needed, I suspect not. But I thought I was going to be using the Invoke-Command PowerShell statement to copy the files on a remote server, but left my changes from the articles above that I did intact mostly I believe.
You will need a dedicated User setup in Active Directory, and to configure the user accounts that SQL Server and SQL Server Agent are running under to give the main calling PowerShell the privileges needed to access the network and other things to, and can be used to run the PowerShell on the remote server as well. And you may need to configure SQLServer to allow SQL Server Jobs or Stored Procedures to be able to call PowerShell scripts like I did. But this is outside the scope of this post. You Google other places on the internet to show you how to do that.

Powershell Using Start-Process in PSSession to Open Notepad

I've created a pssession on a remote computer and entered that possession. From within that session I use start-process to start notepad. I can confirm that notepad is running with the get-process command, and also with taskmgr in the remote computer. However, the GUI side of the process isn't showing. This is the sequence I've been using:
$server = New-PSSession -ComputerName myserver -Credential mycreds
Enter-PSSession $server
[$server]: PS C:\>Start-Process notepad -Wait -WindowStyle Maximized
The process is running, but while RDP'd to the box, notepad does not open. If I open notepad from the server, a new notepad process begins. I also tried by using the verb parameter like this:
[$server]: PS C:\>Start-Process notepad -Wait -WindowStyle Maximized -Verb Open
Same result tho... Process starts, but no notepad shows. I've tried this while remoted into the box (but issued from my local host) as well as before remoting into the server.
That is because your powershell session on the remote machine does not go to any visible desktop, but to an invisible system desktop. The receiving end of your powershell remote session is a Windows service. The process is started, but nor you nor anyone else can ever see it.
And if you think about it, since multiple users could RDP to the same machine, there is really no reason to assume a remote powershell session would end up showing on any of the users desktops. Actually, in almost all cases you wouldn't want it anyway.
psexec with the -i parameter is able to do what you want, but you have to specify which of the sessions (users) you want it to show up in.
I know this is old, but I came across it looking for the solution myself so I wanted to update it for future poor souls.
A native workaround for this problem is to use a scheduled task. That will use the active session
function Start-Process-Active
{
param
(
[System.Management.Automation.Runspaces.PSSession]$Session,
[string]$Executable,
[string]$Argument,
[string]$WorkingDirectory,
[string]$UserID
)
if (($Session -eq $null) -or ($Session.Availability -ne [System.Management.Automation.Runspaces.RunspaceAvailability]::Available))
{
$Session.Availability
throw [System.Exception] "Session is not availabile"
}
Invoke-Command -Session $Session -ArgumentList $Executable,$Argument,$WorkingDirectory,$UserID -ScriptBlock {
param($Executable, $Argument, $WorkingDirectory, $UserID)
$action = New-ScheduledTaskAction -Execute $Executable -Argument $Argument -WorkingDirectory $WorkingDirectory
$principal = New-ScheduledTaskPrincipal -userid $UserID
$task = New-ScheduledTask -Action $action -Principal $principal
$taskname = "_StartProcessActiveTask"
try
{
$registeredTask = Get-ScheduledTask $taskname -ErrorAction SilentlyContinue
}
catch
{
$registeredTask = $null
}
if ($registeredTask)
{
Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false
}
$registeredTask = Register-ScheduledTask $taskname -InputObject $task
Start-ScheduledTask -InputObject $registeredTask
Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false
}
}
When you use New-PSSession and then RDP into that same computer, you're actually using two separate and distinct user login sessions. Therefore, the Notepad.exe process you started in the PSSession isn't visible to your RDP session (except as another running process via Task Manager or get-process).
Once you've RDP'd into the server (after doing what you wrote in your post), start another Notepad instance from there. Then drop to PowerShell & run this: get-process -name notepad |select name,processid
Note that there are two instances, each in a different session.
Now open up Task Manager and look at the user sessions. Your RDP session will probably be listed as session 1.
Now quit Notepad and run get-process again. You'll see one instance, but for session 0. That's the one you created in your remote PSSession.
There are only 2 workarounds that I know of that can make this happen.
Create a task schedule as the logged in user, with no trigger and trigger it manually.
Create a service that starts the process with a duplicated token of the logged in user.
For the task schedule way I will say that new-scheduledtask is only available in Windows 8+. For windows 7 you need to connect to the Schedule Service to create the task like this (this example also starts the task at logon);
$sched = new-object -ComObject("Schedule.Service")
$sched.connect()
$schedpath = $sched.getFolder("\")
$domain = "myDomain"
$user="myuser"
$domuser= "${domain}\${user}"
$task = $sched.newTask(0) # 0 - reserved for future use
$task.RegistrationInfo.Description = "Start My Application"
$task.Settings.DisallowStartIfOnBatteries=$false
$task.Settings.ExecutionTimeLimit="PT0S" # there's no limit
$task.settings.priority=0 # highest
$task.Settings.IdleSettings.StopOnIdleEnd=$false
$task.settings.StopIfGoingOnBatteries=$false
$trigger=$task.Triggers.create(9) # 9 - at logon
$trigger.userid="$domuser" # at logon
$action=$task.actions.create(0) # 0 - execute a command
$action.path="C:\windows\system32\cmd.exe"
$action.arguments='/c "c:\program files\vendor\product\executable.exe"'
$action.WorkingDirectory="c:\program files\vendor\product\"
$task.principal.Id="Author"
$task.principal.UserId="$domuser"
$task.principal.LogonType=3 # 3 - run only when logged on
$task.principal.runlevel=1 # with elevated privs
# 6 - TASK_CREATE_OR_UPDATE
$schedpath.RegisterTaskDefinition("MyApplication",$viztask,6,$null,$null,$null)
Creating a service is way more complicated, so I'll only outline the calls needed to make it happen. The easy way is to use the invoke-asservice script on powershell gallery: https://www.powershellgallery.com/packages/InvokeAsSystem/1.0.0.0/Content/Invoke-AsService.ps1
Use WTSOpenServer and WTSEnumerateSessions to get the list of sessions on the machine. You also need to use WTSQuerySessionInformation on each session to get additional information like username. Remember to free your resources using WTSFreeMemory and WTSCloseServer You'll end up with some data which looks like this (this is from the qwinsta command);
SESSIONNAME USERNAME ID STATE
services 0 Disc
>rdp-tcp#2 mheath 1 Active
console 2 Conn
rdp-tcp 65536 Listen
Here's an SO post about getting this data; How do you retrieve a list of logged-in/connected users in .NET?
This is where you implement your logic to determine which session to target, do you want to display it on the Active desktop regardless of how it's being presented, over RDP or on the local console? And also what will you do if there is no one logged on? (I've setup auto logon and call a lock desktop command at logon so that a logged in user is available.)
You need to find the process id of a process that is running on the desktop as that user. You could go for explorer, but your machine might be Server Core, which explorer isn't running by default. Also not a good idea to target winlogon because it's running as system, or dwm as it's running as an unprivileged user.
The following commands need to run in a service as they require privileges that only system services have. Use OpenProcess to get the process handle, use OpenProcessToken to get the security token of the process, duplicate the token using DuplicateTokenEx then call ``CreateProcessAsUser``` and finally Close your handles.
The second half of this code is implemented in invoke-asservice powershell script.
You can also use the sysinternals tool psexec, I didn't list it as a 3rd way because it just automates the process of creating a service.