While Loop with Break Statement in PowerShell [duplicate] - powershell

I am trying to build my own script to check some Windows services (status and start mode) and I am facing an issue on the IF ...
For example even if the service is "Running", it will never run the code inside the IF...
let me share my code below (I am a newbie on powershell so be gentle xD)
For info, I will do more actions inside the IF and ELSE, it is just for the example.
# import computers list, 1 by line
$Computers = get-content .\computers.txt
# define variable of services we want to check
$ServiceNetbios = "netbt"
# define variable to ask credentials
$Cred = Get-Credential
# declare Function to open a session a remote computer
Function EndPSS { Get-PSSession | Remove-PSSession }
EndPSS
########################################################
# BEGINNING OF SCRIPT #
# by xxx #
# 2022-02-03 #
########################################################
# loop for each computer imported from the file
foreach ($computer in $computers) {
# show name of computer in progress
$computer
# connect remotely to the computer
$session = New-PSSession -ComputerName $computer -Credential $Cred
# check Netbios service
$StatusServiceNetbios = Invoke-Command -Session $session -ScriptBlock { Get-Service -Name $Using:ServiceNetbios | select -property * }
# Check Netbios service started or not
write-host $StatusServiceNetbios.Status
if ($StatusServiceNetbios.Status -eq 'Running')
{
Write-host "IF Running"
}
else
{
write-host "IF NOT Running"
}
EndPSS
}
and what return my script :
computername
Running (<= the variable $StatusServiceNetbios.Status )
IF NOT Running (<= the ELSE action)
Thanks you in advance for your help,
this drive me crazy and maybe this is very simple...

To complement Cpt.Whale's helpful answer, this is likely to be caused by the serialization and deserialization done by Invoke-Command:
using namespace System.Management.Automation
$service = Get-Service netbt
$afterInvokeCmd = [PSSerializer]::Deserialize(([PSSerializer]::Serialize($service)))
$service.Status -eq 'Running' # => True
$afterInvokeCmd.Status -eq 'Running' # => False
$afterInvokeCmd.Status.Value -eq 'Running' # => True
$afterInvokeCmd.Status.ToString() -eq 'Running' # => True
To put some context to my answer, this is a nice quote from about_Remote_Output that can better explain why and what is happening:
Because most live Microsoft .NET Framework objects (such as the objects that PowerShell cmdlets return) cannot be transmitted over the network, the live objects are "serialized". In other words, the live objects are converted into XML representations of the object and its properties. Then, the XML-based serialized object is transmitted across the network.
On the local computer, PowerShell receives the XML-based serialized object and "deserializes" it by converting the XML-based object into a standard .NET Framework object.
However, the deserialized object is not a live object. It is a snapshot of the object at the time that it was serialized, and it includes properties but no methods.

This is probably because of the way powershell creates service objects - (Get-Service netbt).Status has a child property named Value:
$StatusServiceNetbios.Status
Value
-----
Running
# so Status is never -eq to 'Running':
$StatusServiceNetbios.Status -eq 'Running'
False
# use the Value property in your If statement instead:
$StatusServiceNetbios.Status.Value -eq 'Running'
True

Related

Providing test cases to Pester V5 test

I'm trying to write a pester test (v5) to see if various services are running on remote computers. This is what I have, which works:
$Hashtable = #(
#{ ComputerName = "computer1"; ServiceName = "serviceA" }
#{ ComputerName = "computer1"; ServiceName = "serviceB" }
#{ ComputerName = "computer2" ; ServiceName = "serviceB" }
)
Describe "Checking services" {
It "check <ServiceName> is running on <ComputerName>" -TestCases $Hashtable {
( get-service -computername $ComputerName -name $ServiceName ).status | Should -be "Running"
}
}
My question is around providing the test data to the test (i.e. the list of computer names and services). Suppose I want to add more services to this list. At the moment, I would be modifying my pester file by adding more services to $Hashtable. It doesn't feel quite right to be doing this to me, and I'd like to get the approach correct at this early stage. My gut tells me that the list of services should be separated from the pester file. Then running the test would involve importing the list of services somehow. Does anyone know if I am going about this the wrong way?
Thanks for any help
Andrew
If the list of servers and services will change often, it would be a good idea to read it from a separate file, especially if you have the tests under version control. This way you can easily see in the history that only the test data has changed, but the test logic didn't.
A good file format for the given test data would be CSV:
ComputerName, ServiceName
computer1, serviceA
computer1, serviceB
computer2, serviceB
You can read the CSV using Import-Csv, but you have to convert each row to a hashtable, because Pester expects an array of hashtables for the -TestCases parameter. Import-Csv outputs an array of PSCustomObject though.
BeforeDiscovery {
$script:testCases = Import-Csv $PSScriptRoot\TestCases.csv | ForEach-Object {
# Convert row (PSCustomObject) to hashtable.
$hashTable = #{}
$_.PSObject.Properties | ForEach-Object { $hashTable[ $_.Name ] = $_.Value }
# Implicit output that will be captured in array $script:testCases
$hashTable
}
}
Describe "Checking services" {
It "check <ServiceName> is running on <ComputerName>" -TestCases $script:testCases {
( get-service -computername $ComputerName -name $ServiceName ).status | Should -be "Running"
}
}
Note: While not strictly necessary I have put the code that reads the test cases into the BeforeDiscovery section, as suggested by the docs. This makes our intentions clear.

How to Sync Input of Line X of a Reference Text File to Number X Text File of Folder [duplicate]

So i have a powershell script im toying with that i'd like to ask the community's help. I have to preface that I am not always the best in communicating what I am attempting to do, partly because i dont have programming experience so please bear with me, and ask questions/correct me if i use incorrect words to explain what i mean.
With that said, Here's what I am trying to do:
while incrementing both services and startuptypes:
stop service A ($services) on server X ($rebootingServer)
Disable service A ($services) on server X ($rebootingServer)
Given: we know service A is disabled on server Y prior to script running
Enable service A on server Y based on text file list $startuptypes
Start service A on server Y
Rinse and repeat until $services and $startuptypes are at the end of each list
So assume $services has:
bits
appmgmt
and $startuptypes has:
Automatic
Manual
i want them to be applied respectively (bits > automatic appmgmt > manual)
Heres what i have thus far:
$services = Get-Content "C:\TEMP\services.txt"
$Startuptypes = Get-Content "C:\TEMP\StartupTypes.txt"
$RebootingServer = Read-Host 'Name of the server that you are bringing down'
$FailoverServer = Read-Host 'Name of the server it is failing over to'
#foreach ($service in $services && $Startuptype in $Startuptypes) {
Invoke-Command -ComputerName $RebootingServer -ArgumentList $service - ScriptBlock {param($service) Stop-Service $service}
Start-Sleep -s 3
Invoke-Command -ComputerName $RebootingServer -ArgumentList $service - ScriptBlock {param($service) set-service $service -StartupType Disabled}
Start-Sleep -s 10
Invoke-Command -ComputerName $FailoverServer -ArgumentList $service $StartupType -ScriptBlock {param($service,$startuptype) Set-Service $service -StartupType $startuptype}
Start-Sleep -s 3
Invoke-Command -ComputerName $FailoverServer -ArgumentList $service - ScriptBlock {param($service) Start-Service $service}
Start-sleep -s 10
}
The 'for each' statement is pseudo-code of what i want it to do, yet unsure if that exists or how to write it accordingly. I dont even know what that would be appropriately called. Multiple conditionals? That aside, how would i properly write what I am attempting to accomplish? Thank you for any help in advanced.
It sounds like you want to enumerate the elements of 2 collections in corresponding pairs: in iteration 1, process element 1 from collection A along with element 1 from collection B, ...
Note:
This answer assumes that the input collections have the same length. See this answer for a solution if they do not.
PowerShell 6- solution:
# Sample collections.
# Note that their counts must match.
$services = 'serviceA', 'serviceB', 'serviceC'
$startupTypes = 'automatic', 'manual', 'disabled '
$i = 0 # helper index var.
foreach ($service in $services) { # enumerate $services directly
# Using the index variable, find the corresponding element from
# the 2nd collection, $startupTypes, then increment the index.
$startupType = $startupTypes[$i++]
# Now process $service and $startupType as needed.
}
PowerShell 7+ solution:
PowerShell 7 is built on .NET Core 3.1, where the System.Linq.Enumerable.Zip has an overload that returns (2-element) value tuples (System.ValueTuple<T1, T2>), which simplifies the solution:
Caveat: With input collections of unequal length, enumeration stops once the smaller collection has run out of items.
# Sample collections.
# Note: Due to use of [Linq.Enumerable]::Zip() below:
# * The element counts need *not* match, because pairing stops
# once the shorter collection has run out of elements.
# * However, note that strongly typed type constraint ([string[]]),
# which is required for PowerShell to find the right [Linq.Enumerable]::Zip()
# method overload. You can also *cast* on demand.
[string[]] $services = 'serviceA', 'serviceB', 'serviceC'
[string[]] $startupTypes = 'automatic', 'manual', 'disabled '
# Enumerate the collection in pairs (2-element value tuples).
# Note that the properties containing the tuple elements are
# named .Item1 and .Item2, but PowerShell allows you to access them
# by positional index too.
[Linq.Enumerable]::Zip($services, $startupTypes) | ForEach-Object {
"service: {0}; startup type: {1}" -f $_[0], $_[1]
}
Generally, note that PowerShell, as of 7.3, lacks support for .NET extension methods (which is why explicit use of [System.Linq.Enumerable] is required here) and also lacks comprehensive support for calling generic methods, though there are feature requests on GitHub - see GitHub issue #2226 and GitHub issue #5146.
The above yields:
service: serviceA; startup type: automatic
service: serviceB; startup type: manual
service: serviceC; startup type: disabled

Powershell foreach regarding multiple collections

So i have a powershell script im toying with that i'd like to ask the community's help. I have to preface that I am not always the best in communicating what I am attempting to do, partly because i dont have programming experience so please bear with me, and ask questions/correct me if i use incorrect words to explain what i mean.
With that said, Here's what I am trying to do:
while incrementing both services and startuptypes:
stop service A ($services) on server X ($rebootingServer)
Disable service A ($services) on server X ($rebootingServer)
Given: we know service A is disabled on server Y prior to script running
Enable service A on server Y based on text file list $startuptypes
Start service A on server Y
Rinse and repeat until $services and $startuptypes are at the end of each list
So assume $services has:
bits
appmgmt
and $startuptypes has:
Automatic
Manual
i want them to be applied respectively (bits > automatic appmgmt > manual)
Heres what i have thus far:
$services = Get-Content "C:\TEMP\services.txt"
$Startuptypes = Get-Content "C:\TEMP\StartupTypes.txt"
$RebootingServer = Read-Host 'Name of the server that you are bringing down'
$FailoverServer = Read-Host 'Name of the server it is failing over to'
#foreach ($service in $services && $Startuptype in $Startuptypes) {
Invoke-Command -ComputerName $RebootingServer -ArgumentList $service - ScriptBlock {param($service) Stop-Service $service}
Start-Sleep -s 3
Invoke-Command -ComputerName $RebootingServer -ArgumentList $service - ScriptBlock {param($service) set-service $service -StartupType Disabled}
Start-Sleep -s 10
Invoke-Command -ComputerName $FailoverServer -ArgumentList $service $StartupType -ScriptBlock {param($service,$startuptype) Set-Service $service -StartupType $startuptype}
Start-Sleep -s 3
Invoke-Command -ComputerName $FailoverServer -ArgumentList $service - ScriptBlock {param($service) Start-Service $service}
Start-sleep -s 10
}
The 'for each' statement is pseudo-code of what i want it to do, yet unsure if that exists or how to write it accordingly. I dont even know what that would be appropriately called. Multiple conditionals? That aside, how would i properly write what I am attempting to accomplish? Thank you for any help in advanced.
It sounds like you want to enumerate the elements of 2 collections in corresponding pairs: in iteration 1, process element 1 from collection A along with element 1 from collection B, ...
Note:
This answer assumes that the input collections have the same length. See this answer for a solution if they do not.
PowerShell 6- solution:
# Sample collections.
# Note that their counts must match.
$services = 'serviceA', 'serviceB', 'serviceC'
$startupTypes = 'automatic', 'manual', 'disabled '
$i = 0 # helper index var.
foreach ($service in $services) { # enumerate $services directly
# Using the index variable, find the corresponding element from
# the 2nd collection, $startupTypes, then increment the index.
$startupType = $startupTypes[$i++]
# Now process $service and $startupType as needed.
}
PowerShell 7+ solution:
PowerShell 7 is built on .NET Core 3.1, where the System.Linq.Enumerable.Zip has an overload that returns (2-element) value tuples (System.ValueTuple<T1, T2>), which simplifies the solution:
Caveat: With input collections of unequal length, enumeration stops once the smaller collection has run out of items.
# Sample collections.
# Note: Due to use of [Linq.Enumerable]::Zip() below:
# * The element counts need *not* match, because pairing stops
# once the shorter collection has run out of elements.
# * However, note that strongly typed type constraint ([string[]]),
# which is required for PowerShell to find the right [Linq.Enumerable]::Zip()
# method overload. You can also *cast* on demand.
[string[]] $services = 'serviceA', 'serviceB', 'serviceC'
[string[]] $startupTypes = 'automatic', 'manual', 'disabled '
# Enumerate the collection in pairs (2-element value tuples).
# Note that the properties containing the tuple elements are
# named .Item1 and .Item2, but PowerShell allows you to access them
# by positional index too.
[Linq.Enumerable]::Zip($services, $startupTypes) | ForEach-Object {
"service: {0}; startup type: {1}" -f $_[0], $_[1]
}
Generally, note that PowerShell, as of 7.3, lacks support for .NET extension methods (which is why explicit use of [System.Linq.Enumerable] is required here) and also lacks comprehensive support for calling generic methods, though there are feature requests on GitHub - see GitHub issue #2226 and GitHub issue #5146.
The above yields:
service: serviceA; startup type: automatic
service: serviceB; startup type: manual
service: serviceC; startup type: disabled

What's the fastest way to get online computers

I'm writing a function which returns all Online Computers in our network, so I can do stuff like this:
Get-OnlineComputers | % { get-process -computername $_ }
Now I basically got my function ready, but it's taking way too long.
I want to only return Computers which have WinRM active, but I also want to provide the option to get every computer even those which haven't got WinRM set up (switch parameter).
This is my function. first it creates a pssession to the domaincontroller, to get all computers in our LAN. then foreach computer, it will test if they have WinRM active or if they accept ping. if so, it gets returned.
$session = New-PSSession Domaincontroller
$computers = Invoke-Command -Session $session { Get-ADComputer -filter * } | select -ExpandProperty Name
$computers | % {
if ($IncludeNoWinRM.IsPresent)
{
$ErrorActionPreference = "SilentlyContinue"
$ping = Test-NetConnection $_
if ($ping.PingSucceeded -eq 'True')
{
$_
}
}
else
{
$ErrorActionPreference = "SilentlyContinue"
$WinRM = Test-WSMan $_
if ($WinRM)
{
$_
}
}
}
Is this the best way I can go to check my online computers? Does anyone have a faster and better idea?
Thanks!
Very Quick Solution is using the -Quiet Parameter of the Test-Connection cmdlet:
so for example:
$ping = Test-Connection "Computer" -Quiet -Count 1
if ($ping)
{
"Online"
}
else
{
"Offline"
}
if it's not enough fast for you, you can use the Send Method of the System.Net.NetworkInformation.Ping
here's a sample function:
Function Test-Ping
{
Param($computer = "127.0.0.1")
$ping = new-object System.Net.NetworkInformation.Ping
Try
{
[void]$ping.send($computer,1)
$Online = $true
}
Catch
{
$Online = $False
}
Return $Online
}
Regarding execute it on multiple computers, I suggest using RunSpaces, as it's the fastest Multithreading you can get with PowerShell,
For more information see:
Runspaces vs Jobs
Basic Runspaces implemenation
Boe Prox (master of runspaces) has written a function which is available from the Powershell Gallery. I've linked the script below.
He uses many of the answers already given to achieve the simultaneous examination of 100s of computers by name. The script gets WMI network information if test-connection succeeds. It should be fairly easy to adapt to get any other information you want, or just return the result of the test-connection.
The script actually uses runspace pools rather than straight runspaces to limit the amount of simultaneous threads that your loop can spawn.
Boe also wrote the PoSH-RSJob module already referenced. This script will achieve what you want in native PoSH without having to install his module.
https://gallery.technet.microsoft.com/scriptcenter/Speedy-Network-Information-5b1406fb

Remote Powershell return value

I want to run a powershell with WinRM on serveral servers. I am using Invoke-Command with a script block.
I am reading from IIS and want to return a AppPool-Object, but I cannot access its properties - always empty.
#--imagine this code in a foreach block
$result = Invoke-Command -ComputerName $line.Servername -ScriptBlock {
Import-Module WebAdministration
$remotePoolName = Get-Item "IIS:\Sites\LeSite" #| Select-Object applictionPool
$pool = dir IIS:\AppPools | Where-Object { $_.Name -eq $remotePoolName.applicationPool }
return $pool
}
write-host $result.managedRuntimeVersion <- empty
Do I have to access it on the remote machine and return it as string ?
The problem here is, that you are referring to a property including get and set functions.
Using these functions outside of your server area results in nothing since the object is no longer in your server environment.
Using these functions inside your script block will work, because you use them on your server directly.
Greetz