Install program remotely using Invoke-Command - powershell

The variable at the top of the script defines several commands/variables for New-PSDrive, as well as connection and installation.
After this, a function is created to open a text file and extract information out of it. I know this part works because I use it in 2 other scripts.
Lastly, The script executes the commands in the first variable.
The script will show as running successfully, but checking the remote computer reveals that nothing happened.
Prior to doing any of this activity, the remote computer has a script run against it that:
enables PSRemoting (setting firewall rules and starting WinRM), and
bypasses execution policies.
After those steps, the script below is run to install a piece of software.
$eAudIT2014V2Install = {
$eAudIT2014V2password = ConvertTo-SecureString "PasswordHere" -AsPlainText -Force
$eAudIT2014V2cred = New-Object System.Management.Automation.PSCredential('domain\user', $eAudIT2014V2password)
$eAudIT2014V2drive = New-PSDrive -Name eAudIT2014V2 -PSProvider FileSystem -Root "\\Server\Share" -Credential $eAudIT2014V2cred
$eAudIT2014V2job = Start-Job {"eAudIT2014V2:\Setup.cmd"}
Wait-Job $eAudIT2014V2job
Receive-Job $eAudIT2014V2job
}
Function Get-OpenFile($initialDirectory) {
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = $initialDirectory
$OpenFileDialog.ShowDialog()
$OpenFileDialog.Filename
$OpenFileDialog.ShowHelp = $true
}
$InputFile = Get-OpenFile
if ($InputFile -eq "Cancel") {
Write-Host "Canceled By User"
exit
} else {
$Computers = #(Get-Content -Path $InputFile)
}
foreach ($computer in $computers) {
Write-Host "Installing eAudIT 2014V2 on Selected Computers"
Invoke-Command $eAudIT2014V2Install
}
I'm noticing that if I tell this script to run something basic like notepad.exe, a dllhost process starts on the machine, but notepad never does. What am I doing wrong?

The answer is pretty simple here. All of your script is for naught if you don't tell the Invoke-Command cmdlet what computer you want to execute the code on. As it is you are simply iterating a loop and invoking that command X number of times on the local machine. You need to change that second to the last line to specify the machine to execute the code on:
Invoke-Command $eAudIT2014V2Install -ComputerName $computer

Related

Running commands as logged on user (remotely)

Thought I would share this quick function I made for myself, feel free to adapt it and improve it according to your needs.
Sometimes you want to run commands as the logged on user of a remote computer.
As you know, some commands show output for the user who runs it and if you run the same command with Invoke-Command, it won't return the user's information, but yours). Get-Printer is an example amongst many others.
There is no easy, quick way of running commands as the logged on user natively without any third-party apps like PsExec or others so I made this quick function that uses VBS, PS1 and Scheduled Task to make it happen.
It runs completly silently for the user (thanks to the VBS) and the output is shown in your console. Please note it assumes the remote computer has a C:\TEMP.
Created in a Windows 10, powershell v 5.1.17763.503 environement.
I don't pretend it's final and perfect, it's the simplest way I found to do what is needed and I just wanted to share it with you guys as it can be very useful!
Check the comments for explanation of the code and feel free to use it as you wish. Please share your version as I'm curious to see people improve it. A good idea would be to make it support multiple computers, but as I said it's a quick function I did I don't have too much time to put into refining it.
That being said, I had no problems using it multiple times as is :)
*Output returned is in form of a string, if you want to have a proper object, add '| ConvertFrom-String' and play with it :)
PLEASE NOTE: The surefire way of grabbing the username of who is currently logged on is via QWINSTA (since Win32_ComputerSystem - Username is only reliable if a user is logged on LOCALLY, it won't be right if a user is using RDP/RemoteDesktop). So this is what I used to grab the username, however, please note that in our french environement the name of the username property in QWINSTA is "UTILISATEUR",so you have to change that to your needs (english or other language) for it to work. If I remember correctly, it's "USERNAME" in english.
On this line:
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
See code in the answer below.
function RunAsUser {
Param ($ComputerName,$Scriptblock)
#Check that computer is reachable
Write-host "Checking that $ComputerName is online..."
if (!(Test-Connection $ComputerName -Count 1 -Quiet)) {
Write-Host "$ComputerName is offline" -ForegroundColor Red
break
}
#Check that PsRemoting works (test Invoke-Command and if it doesn't work, do 'Enable-PsRemoting' via WMI method).
#*You might have the adjust this one to suit your environement.
#Where I work, WMI is always working, so when PsRemoting isn't, I enable it via WMI first.
Write-host "Checking that PsRemoting is enabled on $ComputerName"
if (!(invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)) {
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList "powershell.exe -command Enable-PSRemoting -SkipNetworkProfileCheck -Force" | Out-Null
do {
Start-Sleep -Milliseconds 200
} until (invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)
}
#Check that a user is logged on the computer
Write-host "Checking that a user is logged on to $ComputerName..."
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
if (!($LoggedOnUser) ) {
Write-Host "No user is logged on to $ComputerName" -ForegroundColor Red
break
}
#Creates a VBS file that will run the scriptblock completly silently (prevents the user from seeing a flashing powershell window)
#"
Dim wshell, PowerShellResult
set wshell = CreateObject("WScript.Shell")
Const WindowStyle = 0
Const WaitOnReturn = True
For Each strArg In WScript.Arguments
arg = arg & " " & strArg
Next 'strArg
PowerShellResult = wshell.run ("PowerShell " & arg & "; exit $LASTEXITCODE", WindowStyle, WaitOnReturn)
WScript.Quit(PowerShellResult)
"# | out-file "\\$ComputerName\C$\TEMP\RAU.vbs" -Encoding ascii -force
#Creates a script file from the specified '-Scriptblock' parameter which will be ran as the logged on user by the scheduled task created below.
#Adds 'Start-Transcript and Stop-Transcript' for logging the output.
$Scriptblock = "Start-Transcript C:\TEMP\RAU.log -force" + $Scriptblock + "Stop-Transcript"
$Scriptblock | out-file "\\$ComputerName\C$\TEMP\RAU.ps1" -Encoding utf8 -force
#On the remote computer, create a scheduled task that runs the .ps1 script silently in the user's context (with the help of the vbs)
Write-host "Running task on $ComputerName..."
Invoke-Command -ComputerName $ComputerName -ArgumentList $LoggedOnUser -ScriptBlock {
param($loggedOnUser)
$SchTaskParameters = #{
TaskName = "RAU"
Description = "-"
Action = (New-ScheduledTaskAction -Execute "wscript.exe" -Argument "C:\temp\RAU.vbs C:\temp\RAU.ps1")
Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd)
RunLevel = "Highest"
User = $LoggedOnUser
Force = $true
}
#Register and Start the task
Register-ScheduledTask #SchTaskParameters | Out-Null
Start-ScheduledTask -TaskName "RAU"
#Wait until the task finishes before continuing
do {
Write-host "Waiting for task to finish..."
$ScheduledTaskState = Get-ScheduledTask -TaskName "RAU" | Select-Object -ExpandProperty state
start-sleep 1
} until ( $ScheduledTaskState -eq "Ready" )
#Delete the task
Unregister-ScheduledTask -TaskName "RAU" -Confirm:$false
}
Write-host "Task completed on $ComputerName"
#Grab the output of the script from the transcript and remove the header (first 19) and footer (last 5)
$RawOutput = Get-Content "\\$ComputerName\C$\temp\RAU.log" | Select-Object -Skip 19
$FinalOutput = $RawOutput[0..($RawOutput.length-5)]
#Shows output
return $FinalOutput
#Delete the output file and script files
Remove-Item "\\$ComputerName\C$\temp\RAU.log" -force
Remove-Item "\\$ComputerName\C$\temp\RAU.vbs" -force
Remove-Item "\\$ComputerName\C$\temp\RAU.ps1" -force
}
#____________________________________________________
#Example command
#Note: Sometimes Start-Transcript doesn't show the output for a certain command, so if you run into empty output, add: ' | out-host' or '| out-default' at the end of the command not showing output.
$Results = RunAsUser -ComputerName COMP123 -Scriptblock {
get-printer | Select-Object name,drivername,portname | Out-host
}
$Results
#If needed, you can turn the output (which is a string for the moment) to a proper powershell object with ' | ConvertFrom-String'

Import-PSSession - throwing credentials-mask

I got a problem with a powershell-script used in our domain to create new users:
Helpdesk will call a .bat as administrator, this bat calls a script-file to automate the creation. In this script, two sessions are created and imported, to use the Exchange- and AD-cmdlets locally.
During/after the import a second/third credential-mask gets thrown, but clicking "cancel" will do nothin, the script will run through without any issues. Nevertheless this annoys the helpdesk..
When running the .ps1 directly from the ISE, the mask won't be shown. Also, when C&Ping the Create-/Import part of the script to a new file and calling it the same way as before also won't show these mask..
Here a part of the .ps1-file:
<#
.DESCRIPTION
Creates a new standard user
.NOTES
Requires : Exchange 2016 Remote Session
Req.OS Version : not tested
Req.PS Version : not tested
.EXAMPLE
Create-User.ps1 -datapath \\path\to\userdata.csv -credentialobject $cred
#>
Param (
[string]$datapath, <#Folder where the CSVs sit #>
[System.Management.Automation.CredentialAttribute()]$credentialobject = $null
)
#region SET global var definitions
$ErrorActionPreference = "Continue"
Start-Transcript -path $ScriptLogPath # | out-null
#endregion
#region SET var definitions
$userfile = "$datapath\userdata.txt"
$groupfile = "$datapath\groupdata.txt"
#Exchange
$MSXremotingserver = "exchangehostname"
$MSXdatabasenames = #("msx_db")
#AD
$domaincontroller = "dchostname"
$ADremotingserver = $domaincontroller
$BaseDN = "OU=Users,DC=domain,DC=local"
#endregion
#region Import Userdata
# CSV's are getting imported here - WORKING
#endregion
#region INIT Remotesession
#Get AD Creds / use given AD Creds
if (($credentialobject -ne $null) -and (($credentialobject.GetType()).name -eq "PSCredential")){
$UserCredential = $credentialobject
}else{
$UserCredential = Get-Credential
# Get credentials to create the remote-sessions. Seems to be working.
}
$MSXSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI http://$MSXremotingserver/powershell -Credential $UserCredential
echo "import..."
$null = Import-PSSession $MSXSession -AllowClobber -DisableNameChecking # | out-null
# After the import (Progress bars running through on top of the PS) another credential-mask appearing, "Cancel" makes the script run through without further errors.
echo "OK"
$ADSession = New-PSsession -Computername $ADremotingserver -Credential $UserCredential
Invoke-Command -Command {Import-Module ActiveDirectory -DisableNameChecking} -Session $ADSession # | out-null
echo "import..."
Import-PSSession -Session $ADSession -Module "ActiveDirectory" -Prefix Remote -AllowClobber -DisableNameChecking # | out-null
# After the import (Progress bars running through on top of the PS) another credential-mask appearing, "Cancel" makes the script run through without further errors.
echo "OK"
#AD-user already existing?
if ([bool](get-remoteaduser -LDAPFilter "(SamAccountName=$($userdata.Kuerzel))")){
#Throw custom error - AD-User bereits vorhanden!
}
#build Account...
# AD-user and Mailbox are created and configured. WORKING!
#endregion
#region END Script
Get-PSSession | Remove-PSSession
Stop-Transcript
Write-Host "Beende Skript..."
start-sleep -Seconds 3
exit 10000
#endregion
And here's how the .ps1 is being called:
%systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -file \\helpdeskserver\powershell_userdata$\Create-User.ps1 \\helpdeskserver\path\to\csv"
I don't know what to do. Tried many different versions of each command, tried piping the in/output, nothing will do..
Google doesn't seem to know that behaviour, neither anyone here on Stackoverflow..
Thanks for any tips and help, I'll apprechiate!
Regards, Ting3l
Edit: When starting the .bat-file without administrative rights (Or with right-click -> other user.. -> admin-account) the second/third credential-dialog won't appear, instead I get an "Index out of range"-exception.
Maybe it's unimportant, but you can try to exit the session by Exit-PSSession. After that use exit 1000. Becoaser when you use exit in the session it completes the session (wherein all code after will be ignored, but script will have successful completed)

Powershell: how to export variables to an existing runspace (of a different Powershell ISE Tab) and to an existing remote PSSession in that runspace

I'm trying to setup Powershell ISE to manage few VMs hosted under Hyper-V and using Powershell Direct.
What I'm trying to do is:
collect in one script all relevant "settings" under variables whose name starts with an appropriate prefix, for example "vms";
at the end of this script open a PowerShellTab for each VM to manage (if not already opened);
copy in the runspace of this new Tab all the relevant settings variables (I think this is necessary for the next step);
start a remote PSSession with the VM (if not already started) and copy or update the settings variables also in this remote PSSession;
enter interactively this PSSession to be able to launch my commands (F8) after selecting portions of sample/template scripts.
My script is this one.
$vms = 'VMName1','VMName2'
$vmsCredentials = #{
VMName1 = [pscredential]::new('User1', (ConvertTo-SecureString 'pw1' -AsPlainText -force))
VMName2 = [pscredential]::new('User2', (ConvertTo-SecureString 'pw2' -AsPlainText -force))
}
$vmsInfo1 = 'blah,blah,blah'
$vmsInfo2 = #{}
$vmsInfo3 = #(1,2,3)
$tabPrevious = $psISE.CurrentPowerShellTab
$vms |% {
Write-Information $_
$tab = $psISE.PowerShellTabs |? DisplayName -eq $_
if ($tab -eq $null)
{
# opens new PS ISE Tab
$tab = $psISE.PowerShellTabs.Add()
$psISE.PowerShellTabs.SetSelectedPowerShellTab($tabPrevious)
$tab.DisplayName = $_
$tab.ExpandedScript = $true
$tab.Files.Clear()
# open all files in new tab
$tabPrevious.Files |% FullPath |? { Test-Path $_ } |% {
[void]$tab.Files.Add($_)
}
}
else
{
# exit remote interactive session, if any
if ($tab.Prompt -match "^\[$_\]: ") {
while (!$tab.CanInvoke) { sleep -Milliseconds 100 }
$tab.Invoke('exit')
}
}
# export/update variables to tab
while (!$tab.CanInvoke) { sleep -Milliseconds 100 }
$rs = ($tab.InvokeSynchronous({ $ExecutionContext.Host.Runspace }, $false))[0]
if ($null -ne $rs) {
Get-Variable "vms*" |% { $rs.SessionStateProxy.SetVariable($_.Name, $_.Value) }
}
# start a new remote PSSession for the tab, if not already done
while (!$tab.CanInvoke) { sleep -Milliseconds 100 }
$tab.Invoke("if (`$null -eq `$pchPSSession) { `$pchPSSession = New-PSSession -VMName '$_' -Credential `$vmsCredentials['$_'] }")
# export/update variables to the remote PSSession and enter interactively
while (!$tab.CanInvoke) { sleep -Milliseconds 100 }
$tab.Invoke("Invoke-Command `$pchPSSession { `$args |% { Set-Variable `$_.Name `$_.Value } } -ArgumentList (Get-Variable 'vms*'); Enter-PSSession -Session `$pchPSSession")
# uncomment this line and no error occurs
#[void]$psISE.PowerShellTabs.Remove($tab)
}
Unfortunately this script works well:
the very first time I run the script in a fresh ISE,
or when only one new Tab need to be opened,
or if I close immediately the just added Tab (uncomment last line),
or under Debugger
otherwise the two last PowerShellTab.Invoke fails (Null Reference).
Any idea to solve this error?
Any way to do it better?

Powershell - Not able to pass remote session variable back to local system

I am trying to figure out what I am doing wrong here. Very new to powershell so be gentle... Trying to run a PSSession on remote system (reading in from list of systems). Then trying to return the value for missing patches to my local system to then export to CSV. I am looking solely for a number to be returned. The value gets displayed in the Powershell window when inside the Invoke-command but then at the bottom of the script nothing is shown. Can anyone offer some advice how I can pass that value back to my system to then be able to export to a csv? Any advice would be greatly appreciated.
$array1 = Get-Content "C:\Users\******\Desktop\Server_List.txt"
$ReportResults = New-Object System.Collections.Generic.List[System.Object]
$Searchresult = #()
#parse thru each machine name in
foreach ($MachineName in $array1)
{
Write-host $MachineName
$session = New-PSSession -ComputerName $MachineName
Invoke-Command -Session $session {
Param($ReportResults)
#Get All Assigned updates in $SearchResult
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 and
IsInstalled=0")
Write-Host "total=$($SearchResult.updates.count)"
$ReportResults.add($SearchResult)
} -ArgumentList $ReportResults
Remove-PSSession $session
}
$ReportResults # | export-csv C:\Users\******\Desktop\Compprogs\Test.csv -
Notypeinformation
You've to "mark" parameter as ref. See this link for further info.
Alternativelly you can return your desired value via Write-Output. Example:
$returnValue = Invoke-Command -ScriptBlock {
Write-Output "Hello World"
}
# $returnValue should include "Hello World"
Write-Host $returnValue
Be aware that when you use Write-Ouput multiple times at Invoke-Command $returnValue will include ALL values wrote to the output steram via Write-Ouput.
Hope that helps

How to test writing to a file share path using credential?

I have an array of Credential objects and I would like to test that these credentials have permissions to write a file to a file share.
I was going to do something like
$myPath = "\\path\to\my\share\test.txt"
foreach ($cred in $credentialList)
{
"Testing" | Out-File -FilePath $myPath -Credential $cred
}
but then I discovered that Out-File doesn't take Credential as a parameter. What's the best way to solve this?
You can use New-PSDrive:
$myPath = "\\path\to\my\share"
foreach ($cred in $credentialList)
{
New-PSDrive Test -PSProvider FileSystem -Root $myPath -Credential $Cred
"Testing" | Out-File -FilePath Test:\test.txt
Remove-PSDrive Test
}
Here is asituation where an old exe (net.exe) seems to do better than powershell...
I guess you could try to map a network drive with the credential provided then test to write a file to that drive :
$cred=get-credential
$pass=$cred.GetNetworkCredential().Password
net use q: \\servername\share $pass /user:$cred.username
Use this script taken from Microsofts TechNet Script Center : http://gallery.technet.microsoft.com/scriptcenter/Lists-all-the-shared-5ebb395a
It is a lot easier to alter to fit your needs then to start completely from scratch.
Open up ListSharedFolderPermissions.ps1, and find the three $Properties vars. add a line at the top of each one so you can tell which user your looking at, so it should now look like this:
$Properties = #{'Username' = $Credential.UserName
'ComputerName' = $ComputerName
. . . . . }
Next, add your new Username property to the select-object line (3 times) :
$Objs|Select-Object Username,ComputerName,ConnectionStatus,SharedFolderName,SecurityPrincipal, `
FileSystemRights,AccessControlType
Once youve added those small pieces in the six appropriate places your script is ready to use:
cd c:\Path\where\you\put\ps1\file
$permissions = #()
$myPath = "computername"
foreach ($cred in $credentialList)
{
$permissions += .\ListAllSharedFolderPermission.ps1 -ComputerName $myPath -Credential $cred
$permissions += " "
}
$permissions | Export-Csv -Path "C:\Permission.csv" -NoTypeInformation
Try using the Invoke-Command function. It will take a credential object and allow you to run an arbitrary script block under that command. You can use that to test out writing the file
Invoke-Command -ScriptBlock { "Testing" | Out-File $myPath } -Credential $cred
I think the Invoke-command approach should work. But if nothing works you can try the powershell impersonation module. It successfully impersonates a user for most Powershell commands without the -Credential switch.
A few ideas:
Create your own PowerShell Provider
Impersonate a user and then write to the share (not sure if possible in powershell)
Use net use d:... as #Kayasax has suggested
Use WScript.Network
I'm very interested in the PowerShell provider myself, but I decided to make something real quick so I went with using the WScript.Network library. I used a hash table to track whether a user would be "authenticated" or not.
$credentials = #() # List of System.Net.NetworkCredential objects
$authLog = #{}
$mappedDrive = 'z:'
$tmpFile = $mappedDrive, '\', [guid]::NewGuid(), '.tmp' -join ''
$path = [io.path]::GetPathRoot('\\server\share\path')
$net = new-object -comObject WScript.Network
foreach ($c in $credentials) {
if ($authLog.ContainsKey($c.UserName)) {
# Skipping because we've already tested this user.
continue
}
try {
if (Test-Path $mappedDrive) {
$net.RemoveNetworkDrive($mappedDrive, 1) # 1 to force
}
# Attempt to map drive and write to it
$net.MapNetworkDrive($mappedDrive, $path, $false, $c.UserName, $c.Password)
out-file $tmpFile -inputObject 'test' -force
# Cleanup
Remove-Item $tmpFile -force
$net.RemoveNetworkDrive($mappedDrive, 1)
# Authenticated.
# We shouldn't have reached this if we failed to mount or write
$authLog.Add($c.UserName, 'Authorized')
}
catch [Exception] {
# Unathenticated
$authLog.Add($c.UserName, 'Unauthorized')
}
}
$authLog
# Output
Name Value
---- -----
desktop01\user01 Authorized
desktop01\user02 Unauthorized