If Else within an Invoke-Command Powershell - powershell

I am trying to run the below command to say if this registry key exists then Get-ItemProperty Else Do nothing or Display Text for Testing.
"SQL Server Product Name" = Invoke-Command -ComputerName $Computer -ScriptBlock {If (Test-Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names") { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object -FilterScript { (($_.Publisher -like "Microsoft*") -and ($_.DisplayName -like "Microsoft SQL Server*(*-bit)")) } | Select-Object -first 1 -ExpandProperty DisplayName } else {Write-Host "Blah"}}
The Else doesn't seem to do anything because right now if the reg key does not exist, it puts in {} to the results instead of Blah. I am not exactly sure if the If statement is working at all since I think it may just be running the Get-ItemProperty no matter what since if that path exists, I get the expected results.

The issue with your example is that the Else scriptblock uses Write-Host. When you execute this on a remote machine, the Host is a PowerShell session on that remote computer. You're writing the text to a session with no GUI on a remote machine.
To fix this, just remove the Write-Host cmdlet. The quoted text will be passed back to your local session along with anything else output by the scriptblock when it is executed on the remote session.

Related

How to stop Windows Services via Powershell with AutomaticDelayed start type?

I was able to write a Powershell 2.0 script that that stops certain running services, which I use in an Ansible script. The Ansible script reboots the VM first, then runs the script.
# Get a list of running XYZ_* services and store them in an array.
$runningExaServices = Get-Service | Where-Object {$_.Name -like "*XYZ_*"} | Where-Object {$_.Status -eq "Running"}
# Iterate throgh the services and stop all of them except EXA_Web,
# EXA_Web_APIs, EXA_Nginx, EXA_Redis. We'll stop those separately
Foreach($service in $runningExaServices) {
Write-Host "Stopping: "$service.name
Stop-Service -Name $service.name -Force
$svc = Get-Service | Where-Object {$_.Name -eq $service.name}
Write-Host $svc.name: $svc.status
}
The XYZZ_* services will indeed stop. However, the services with Automatic (Delayed Start) would start running again. What's the secret to keeping them stopped? TIA
Since Get-Service doesn't expose the "Automatic (Delayed start)" configuration, you'll have to rely on just the Automatic value for StartType. If you want them to stay stopped, switch the starttype value to "Manual":
# Get a list of running XYZ_* services and store them in an array.
$runningExaServices = Get-Service -Name "*XYZ_*" | Where-Object {$_.Status -eq "Running"} #| Stop-Service -PassThru
Foreach($service in $runningExaServices) {
#Write-Host "Stopping: $($service.name)"
if ($service.StartType -eq "Automatic") {
$Serice | Set-Service -StartupType 'Manual'
}
Stop-Service -Name $service.name -Force -PassThru
# Write-Host "$($svc.name): $($svc.status)"
}
I'm not too sure when these cmdlets were introduced but, I will go ahead and take a guess that Set-Service was released at the same time as Get-Service; i.e. PowerShell 2.0.
With that said, a simple if condition/statement can check for the starttype value and proceed accordingly by setting it to "Manual". Then, stopping the service(s).
Couple of side notes:
You don't need the loop if you pipe directly to Set-Service, and Stop-Service given the following circumstances:
You don't care about setting the starttype to "Manual"
You don't care about the iteration process, and just having them all stop at once with a -PassThru switch provided; which in turn outputs the object with the new status of "Stopped".
Lastly, - referring back to the 2nd sub-bullet point above - -PassThru is what I would suggest rather than stating what current service object you're on and re-querying the same object for the new status.

Powershell ForEach-Object output

More of a basic question but I can't seem to get the syntax correct. I am trying to go through a list and then output the Computer Name and BDE status with it.
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object { write-host "***" | manage-bde -status }
Is there something I need to put where the *'s are so that it correlates with the computer/object and the status?
Thanks!
Is there something I need to put where the *'s are so that it correlates with the computer/object and the status?
Yes! You're looking for $_:
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object {
Write-Host $_
}
As the name indicates, Write-Host writes output directly to the host application - in the case of powershell.exe it writes directly to the screen buffer - which means it doesn't output anything to downstream cmdlets in a pipeline statement like the one you've constructed.
You'll therefore want two separate statements:
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object {
Write-Host $_
manage-bde -status
}
Now, manage-bde is not a PowerShell cmdlet - it's a windows executable, and it doesn't support managing remote computers.
So we need something in PowerShell that can run manage-bde on the remote machine - my choice would be Invoke-Command:
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object {
Write-Host "Remoting into $_ to fetch BitLocker Drive Encryption status
Invoke-Command -ComputerName $_ -ScriptBlock { manage-bde -status }
}
Here, we instruct Invoke-Command to connect to the computer with whatever name is currently assigned to $_, and then execute manage-bde -status on the remote machine and return the resulting output, if any. Assuming that WinRM/PowerShellRemoting is configured on the remote machines, and the user executing this code locally is a domain account with local admin privileges on the remote computers, this will work as-is.
Further reading:
about_Remote
about_Remote_Requirements
about_Remote_Troubleshooting

Remote command output to text file (remote system)

I know I must be using these commands wrong but I can't seem to find a solution. I believe the issue is with my use of the invoke-command and out-file. I'm trying to check to see if a process is running on multiple remote machines and write their states back to a text file on the host system. Even if it wrote to the remote system I could work with that but I cant seem to get anything.
$MyDomain=’mydomain’
$MyClearTextUsername=’myusername’
$MyClearTextPassword=’mypassword’
$MyUsernameDomain=$MyDomain+’\’+$MyClearTextUsername
$SecurePassword=Convertto-SecureString –String $MyClearTextPassword
-AsPlainText –force
$MyCredentials=New-object System.Management.Automation.PSCredential
$MyUsernameDomain,$SecurePassword
$Servers = ( "server1","server2","server3")
$output = foreach ($Server in $Servers)
{
$Session = New-PSSession -ComputerName $Server -Credential $MyCredentials
Invoke-Command -Session $Session -ScriptBlock
{
Get-Service -Name service | select name, status, PSComputername, Runspaceid
} | Out-File -filepath 'c:\TEMP\check.txt'
}
Write-output $output | Out-File -filepath 'c:\TEMP\check.txt'
edit: I don't believe the last line is needed but I threw it in just to see if I could get anything out.
You are not capturing anything in $output because you are redirecting all of the output from your Invoke-Command to Out-File -filepath 'c:\TEMP\check.txt'. Get-Service doesn't return that much data, especially once's it's been deserialized when it returns from the remote session, so I wouldn't bother with the Select statement. Even if you do want to include the Select statement you are specifying PSComputerName which doesn't get added until the data comes back from the remote system, so you may want to move that Select to outside of the scriptblock and after the Invoke-Command in the pipeline. Plus, since you are outputting with Out-File your local file is being overwritten each time that call is made, so the first server's results are saved, then overwritten by the second server's results, then by the third server's results. After that, since $output has nothing (as all output was redirected to file), you are outputting an empty variable to the same file, effectively erasing the service state of the third server.
But this really all becomes a moot point if the script is run with the credentials that has access to the remote servers. You can specify one or more computer names to the Get-Service cmdlet, so this could become as simple as:
$Results = Get-Service Service -ComputerName 'Server1','Server2','Server3'
$Results | Select name, status, PSComputername, Runspaceid | Set-Content 'C:\TEMP\check.txt'
Just to make sure... you are looking for a service right? Not just a process? Because if it isn't a service you would need to use Get-Process instead of Get-Service.
If you want to output the data to the remote server you could do:
$output = foreach ($Server in $Servers)
{
$Session = New-PSSession -ComputerName $Server -Credential $MyCredentials
Invoke-Command -Session $Session -ScriptBlock
{
Get-Service -Name service | Tee-Object -FilePath C:\Temp\ServiceState.txt
} | select name, status, PSComputername, Runspaceid
}
$output | Out-File -filepath 'c:\TEMP\check.txt'
That should make a file in the C:\Temp folder on each server with the state of the service, as well as pass the information back to the local host, where it is passed to Select and stored in $output. At the end I output $output to file, just as you did.
I guess in the end you could just remove the Out-File call from within your loop, and it would probably do what you want it to.

Why am i receiving RPC server is unavailable error when looping?

I have a Powershell script to find specific servers and their corresponding service accounts. If I modify the script to use a single server and a single service account, the results are what I expect. If I loop thru the servers and accounts, I receive the following error:
#################################################################
# Find Service Account(s) used to start Services on a Server(s) #
#################################################################
$accounts = (Get-Content C:\Users\location\Scripts\Service_Accounts.txt)
Remove-Item -path C:\Users\location\Scripts\ServiceAccountFnd.txt -force -erroraction silentlycontinue
Import-Module ActiveDirectory # Imports the Active Directory PowerShell module #
## Retrieves servers in the domain based on the search criteria ##
$servers=Get-ADComputer -Filter {Name -Like "namehere*"} -property *
## For Each Server, find the services running under the user specified in $account ##
ForEach ($server in $servers) {
Write-Host $server
ForEach ($account in $accounts) {
Write-Host $account
Get-WmiObject Win32_Service -ComputerName $server | Where-Object {$_.StartName -like "*$account*"} | Format-Table -HideTableHeaders -property #{n='ServerName';e={$_.__SERVER}}, StartName, Name -AutoSize | Out-File -FilePath C:\Users\location\Scripts\ServiceAccountFnd.txt -append -Width 150
}
}
Your $server variable does not only contain the hostname, but also all attributes of the AD computer object.
Try to change the ComputerName value to $server.name.
If that doesn't help: Can you confirm, that you used the very same computer in the loop as without the loop, as you described? I'd assume that you try to access another computer, which is not configured as expected.
Besided that, I'd recommend you to use Get-CimInstance rather than Get-WmiObject, as it doesn't use RPC, but WinRM by default. WinRM is more firewall friendly, secure and faster.

test-connection that supports wildcard? workaround?

trying to see if anyone has a known workaround for using the test-connection cmdlet in powershell to ping wildcard entries in DNS.
I'm trying to clean out our DNS db and exported a list from our BIND server and am in the process of just pinging through the 600+ machines to see if anything responds. I made my own simple script but have also found one that works slightly better on this forum. The script works but the cmdlet help files state that the -computername parameter does not support wildcards and sure enough, when i run the script all CNAME records are reporting down/false when they actually should be responding. The code I'm using is below and is kind of messy but I just needed something quick and it works, but I've included it below for reference:
Get-Content -path C:\Work\testy.txt | ForEach-Object { Test-Connection -ComputerName $_ -Count 1 -AsJob } | Get-Job | Receive-Job -Wait | Select-Object #{Name='ComputerName';Expression={$_.Address}},#{Name='Reachable';Expression={if ($_.StatusCode -eq 0) { $true } else { $false }}} |out-file -FilePath c:\work\TEST.txt
As pointed out by briantist, any non-existing record name will do. You could generate a GUID to substitute the * in your record name:
"subdomain.domain.tld","*.domain.tld" |ForEach-Object {
Test-Connection -ComputerName $($_ -replace '\*',"$([guid]::NewGuid())")
}
Your expression for whether it's "Reachable" or not can be simplified as well:
#{Name='Reachable'; Expression={[bool]($_.StatusCode -eq 0)}}