Powershell: waiting for changed directory - powershell

To make it short, I want to connect to a server that is running Virtual Machines and then get a List of all installed machines, the command I use for this is:
Invoke-Command -ScriptBlock {enter-pssession -ComputerName <name>}; Invoke-Command -ScriptBlock {Get-VM} | select-Object -Property name
This line contains two commands at first:
Invoke-Command -ScriptBlock {enter-pssession -ComputerName <name>};
this part connects to the server, and then:
Invoke-Command -ScriptBlock {Get-VM} | select-Object -Property name
This command gets a list of the VMs currently on the server and returns specific properties of these servers.
However, because the connection needs a short time until it is set up, the "get-vm" command is still set in the previous direction and results in an error report.
I want to know if there is a way to wait for ether a command to be finished or for a change in the directory, without having an extra loop running for this time, or waiting for a hard set time.

I don't know why are you trying to do what you are trying to do, what you should do is:
Invoke-Command -SessionName (or -ComputerName) -ScriptBlock {Get-VM | Select-Object -Property name}

Related

How to remotely deploy multiple PowerShell cmdlets/scripts to multiple remote devices?

I'm looking to use a single host server to maintain a PowerShell script, with global variables, that can be interpreted and ran on several other devices in the network cluster.
On the main host I'd like to specifically be able to maintain a list of variables for IP addresses of each other device that I want to run the scripts against, but then how I want to run the script is something I'm having a hard time determining. There are several things I need to do to each other machine in the cluster (change the computer name, modify the time zone and time, configure the network adapters.... there's a decent list of stuff). The commandlets to do the functions on the individual machines is no problem... I have all of that written out and tested. I just don't what my options are for where that script is stored. Preferably, I think I'd like to declare all of the variables for everything that needs to be done on all machines, at the top of the file on the main host. Then I would like to break down everything that needs to be done to each host on the same file, on the main host. I know it will get a little messy, but that would make maintaining the cmdlets for each device much easier, especially when it comes to testing and making changes. Am I trying to do the impossible here??
I learned about using ENABLE-PSSESSION as well as INVOKE-COMMAND, but each seem to have their own challenges. With Enable-PSSession I cannot seem to find a way to wait for the script to connect to each host before it moves on to the next line. I've tried piping in Out-Null, as well as adding a Start-Sleep line. I don't want to have to manually connect to each host and then manually run the list of commands against each host. Invoke-Command doesn't seem to let me break out the SCRIPTBLOCK section into multiple lines.
Is there any suggestion for the best method of accomplishing the desire to run the script from the main host, that performs all of my cmdlets on multiple machines, without any additional human interaction??
Thanks so much!!
-Andrew
EDIT: I found that I can break the ScriptBlock line (contrary to what I thought didn't work yesterday). Here is basically what I'm trying to accomplish, though of course the below does not work when calling the variables from the top of the file:
#Edit These Variables
$NewName_Server2 = "Server2"
$NewName_Server3 = "Server3"
$NewName_Server4 = "Server4"
$IPAddress_Server2 = "10.10.10.2"
$IPAddress_Server3 = "10.10.10.3"
$IPAddress_Server4 = "10.10.10.4"
$TimeZone = "US Eastern Standard Time"
#Do Not Edit These Variables
$Server2 = "192.168.1.2"
$Server3 = "192.168.1.3"
$Server4 = "192.168.1.4"
#Configure Server 2
Invoke-Command -ComputerName $Server2 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server2
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server2
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
#Configure Server 3
Invoke-Command -ComputerName $Server3 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server3
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server3
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
#Configure Server 4
Invoke-Command -ComputerName $Server3 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server3
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server4
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
You can use the using scope to access local variables. I don't know what $local is. Nice try.
$a = 'hi'
invoke-command comp001,comp002 { $using:a }
hi
hi
The other way is using a param, not well documented. Passing arrays is more tricky.
invoke-command comp001,comp002 { param($b) $b } -args $a

PowerShell won't terminate hung process

I have a scenario where a process is stuck every single Monday morning because of an Oracle database so I tried creating a PowerShell script to run every Monday but regardless of getting an error or not, the process remains.
The line I'm attempting to use for the "kill" is:
Get-Process -Name ez0* -ComputerName $server | Stop-Process -Force
Tried doing this locally as well without the -ComputerName.
I'm not getting any errors from this line with or without the -Force it just executes and moves on.
Just doing Get-Process works and I can see it but I can't end it with PowerShell. After many attempts I remotely logged on to the server and just right-clicked the process and chose "End task" which worked just fine.
It is an odd process because it's one out of initial 8 (based on cores) and when you stop the service, all but one of the processes is removed save for the one that is hung.
Try using:
$termproc = (get-wmiobject -ComputerName $server -Class Win32_Process -Filter "name like 'ez0%'"
$termproc.terminate()
You could also just do the below if you don't want to check the processes in the variable first.
(get-wmiobject -ComputerName $server -Class Win32_Process -Filter "name like 'ez0%'").terminate()
Thanks, Tim.

Powershell invoke-command passing an object

I have created a list of objects, $LIST. Each object in the list has several attributes, including FQDN and Services. FQDN is the fully qualified server name and the services are the list of services I want to check on the remote server.
I'll start with:
$LIST = <CALL to Module function to populate the server information>
Next is the call to the invoke-command
Invoke-Command -ComputerName $LIST.FQDN -ScriptBlock {
Write-Host "Working on $($env:ComputerName)"
Get-Service
}
But what I need to do is pass the list of services that correspond to -ComputerName. I know I can use the -ArgumentList and I've tried:
Invoke-Command -ComputerName $LIST.FQDN -ScriptBlock {
Param ([string[]] $ServiceList)
Write-Host "Working on $($env:ComputerName)"
($ServiceList -split(",")).trim() | %{
$svc =Get-Service $_
$Svc
}
} -ArgumentList $LIST.Services
But this passes a list of all the services for every server. I can do this:
$LIST | %{
$Server = $_
Invoke-Command -ComputerName $Server.FQDN -ScriptBlock {
Param ([string[]] $ServiceList)
Write-Host "Working on $($env:ComputerName)"
($ServiceList -split(",")).trim() | %{
$svc =Get-Service $_
$Svc
}
} -ArgumentList $($Server.SERVICES)
}
But then I loose the advantage of parallelism of the invoke-command CmdLet.
How do I pass the list of services for the specific ComputerName being processed?
If $LIST["COMPUTER"].SERVICES return list service of COMPUTER you can test in your scriptblock for every object passed..maybe This but not tested: (in pseudo-code)
Icm -comp $LIST.FQDN -Script {
Param($obj)
$c=$env:computername
$obj["$c"].services
} -arg $LIST
I don't think there is an easy way to pass your list through Invoke-Command, so perhaps you need to think of alternative approaches that will let each target computer run an identical command.
If the list of services is specific to each remote computer, and is the same every time you run the commands then you could simply store the parameter on each target computer in the registry or a file. Ideally you set that up using a configuration manager such as DSC, Puppet, or Chef.
You could dump the parameters out to files on the local computer and let each target computer connect back to a network share and fetch the file corresponding to its name. If a network share connection isn't possible then a simple web service or a database connection would be other ways you could let each computer fetch the service list.
Are there groups of targets that will receive the same list? Say you have 10 different service lists each to be sent to a group of 100 computers. In that case maybe you could choose an arbitrary computer from each group and send those computers an identical command with all of the data. Each computer you target then figures out which group it is in and distributes the identical command across all of its siblings. This would also have the benefit of increasing the parallelism.
Or just create one set of services that is the union of all the service lists, send that to every machine, and filter the results based on which you wanted. That could be the simplest solution:
PS C:\WINDOWS\system32> $allServices = 'WerSvc','WinRM','ZeroConfigService','AnotherService'
PS C:\WINDOWS\system32> Invoke-Command -ComputerName . -ScriptBlock {
Get-Service -Name $args -ErrorAction Ignore
} -ArgumentList $allServices
Status Name DisplayName PSComputerName
------ ---- ----------- --------------
Stopped WerSvc Windows Error Reporting Service localhost
Running WinRM Windows Remote Management (WS-Manag... localhost
Running ZeroConfigService Intel(R) PROSet/Wireless Zero Confi... localhost
will simply ignore any services you asked for that aren't installed on that particular machine.
For the sake of testing, I've created two objects containing a pointer to localhost, and a list of specific services to verify. Goal is to invoke background jobs that will run remotely and in parallel, and verify the provided list of services.
One of the key parts in the script below is the generated array for the Invoke-Command argumentlist, which contains the services array for the associated computer. The rest is pretty much your code. Finally I use Get-Job | Receive-Job to retrieve the output from the Invoke-Command jobs.
$LIST = #()
$Computer1 = New-Object PSObject -Property #{
FQDN = "localhost";
SERVICES = #()
}
$Computer1.SERVICES += "W32Time"
$Computer1.SERVICES += "vmms"
$LIST += $Computer1
$Computer2 = New-Object PSObject -Property #{
FQDN = "localhost";
SERVICES = #()
}
$Computer2.SERVICES += "ShellHWDetection"
$Computer2.SERVICES += "SharedAccess"
$LIST += $Computer2
$LIST | %{
$Server = $_
$arguments = #(,($Server.SERVICES))
Invoke-Command -ComputerName $Server.FQDN -AsJob -ScriptBlock {
Param ([string[]]$ServiceList)
Write-Host "Working on $($env:ComputerName)"
($ServiceList -split(",")).trim() | %{
$svc =Get-Service $_
$Svc
}
} -ArgumentList $arguments
}
Get-Job | Receive-Job
Output is this:
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
28 Job28 RemoteJob Running True localhost ...
30 Job30 RemoteJob Running True localhost ...
Working on CORSAIR-PC
Status Name DisplayName PSComputerName
------ ---- ----------- --------------
Stopped W32Time Windows Time localhost
Running vmms Hyper-V Virtual Machine Management localhost
Working on CORSAIR-PC
Running ShellHWDetection Shell Hardware Detection localhost
Stopped SharedAccess Internet Connection Sharing (ICS) localhost
So, distinct services per computer were passed to Invoke-Command which runs in parallel jobs using the -AsJob parameter.

Get Process StartTime from a remote server using Powershell

I'm using following command to get the start time of a windows process. This is to get the running time of a process to terminate if it running too long.
$ProcessStartTime =(Get-Process $WinProcess -computer $computer).StartTime (Not working)
above code not returning Start Time value from a remote server ( it can access other process information). But it getting values for a local process with following command.
$ProcessStartTime =(Get-Process $WinProcess).StartTime (Working)
Can some one help me.
You can use wmi for this job:
gwmi win32_process -computername $computer|
? { $_.name -eq "powershell.exe" } |
% { $_.ConvertToDateTime( $_.CreationDate )}
i've the same result as you, but you can create a new session on the remote computer then use invoke-command to run your script :
$sess=new-pssession $computer
$ProcessStartTime =invoke-command -session $sess -ScriptBlock{ (Get-Process $WinProcess).StartTime}

Remote Machine not executing program

Here is what I have so far:
$source1="C:\Folder\Files\IPList.txt"
Get-Content $source1 |
Where-Object {-not(gwmi win32_process -ComputerName $_ -filter "name='Program.exe'")} |
Foreach-Object {Invoke-Command -ComputerName $_ -ScriptBlock {"C:\Program Files\Folder\Folder\Program.exe"}}
When I run this in ISE everything comes back as normal and says it has run correctly. However, when I look at my remote machine nothing has been executed. The Process Program.exe is not running and there for the exe should be launched. I am running this from a server to hit about 50 remote machines. Once it goes through all 50, I will loop it and have it do it again, then continue the process in an infinite loop.
What am I missing for the program to start remotely? By the way I am running this script on Server 2008 R2 and it is hitting Windows 7 machines.
Edit
I am wondering since I can see the process firing off, is this an issue with Windows 7? I know Microsoft changed things and a service cannot fire off an application in the User space. Do you think this would be part of the same problem?
Try adding the call (&) operator to the ScriptBlock:
$source1="C:\Folder\Files\IPList.txt"
Get-Content $source1 |
Where-Object {-not(gwmi win32_process -ComputerName $_ -filter "name='Program.exe'")} |
Foreach-Object {Invoke-Command -ComputerName $_ -ScriptBlock {& "C:\Program Files\Folder\Folder\Program.exe"}}
Here's a good article on the various methods available to you: http://social.technet.microsoft.com/wiki/contents/articles/7703.powershell-running-executables.aspx
In your current syntax, the command your passing is just a string! This is what is happening on the remote end:
PS C:\> "C:\Program Files\Console2\Console.exe"
C:\Program Files\Console2\Console.exe
Powershell is echoing your string!
I am going to quote Oliver Lipkau as mentioned here: Source
If you need to start a process on a remote computer that keeps running after the script finished, use this function:
Function New-Process ([string]$computername, [string]$name) {
([WMICLASS]"\\$computername\ROOT\CIMV2:win32_process").Create($name)
}