Powershell Arrays - checking to see if services have stopped - arrays

I know there is a quicker way of doing this but I just don't know how to approach it. I also need it to work in PowerShell V2 as I still have Windows 2003 servers I need to handle with this.
Basically this would stop services and then check to see if those services were stopped. I have a location to define them earlier in the script. I am using this to deploy code to servers so depending on the code from Development I may need to stop 1 to 4 services and looking to see if there is a way that I can define the services and not have to comment out code below if I only use 2 services as opposed to four.
#Turn off Services
stop-service $Service1 -force
# stop-service $Service2 -force
# stop-service $Service3 -force
# stop-service $Service4 -force
$VerifyServiceStopped1 = Get-Service $Service1 | Where-Object {$_.status -eq "Stopped"} | select -last 1
# $VerifyServiceStopped2 = Get-Service $Service2 | Where-Object {$_.status -eq "Stopped"} | select -last 1
# $VerifyServiceStopped3 = Get-Service $Service3 | Where-Object {$_.status -eq "Stopped"} | select -last 1
# $VerifyServiceStopped4 = Get-Service $Service4 | Where-Object {$_.status -eq "Stopped"} | select -last 1
if ($VerifyServiceStopped1) {Write-Host $Service1' Stop = Pass (0)'} else {Write-Host $Service1' Stop = Fail (1000)'; Exit '1000'}
# if ($VerifyServiceStopped2) {Write-Host $Service2' Stop = Pass (0)'} else {Write-Host $Service2' Stop = Fail (1001)'; Exit '1001'}
# if ($VerifyServiceStopped3) {Write-Host $Service3' Stop = Pass (0)'} else {Write-Host $Service3' Stop = Fail (1002)'; Exit '1002'}
# if ($VerifyServiceStopped4) {Write-Host $Service4' Stop = Pass (0)'} else {Write-Host $Service4' Stop = Fail (1003)'; Exit '1003'}
Any suggestions?
Thanks
Dwight

Something like this, perhaps?
$services = #(
'Service1',
'Service2',
'Service3',
'Service4')
Get-service |
Where { $Services -Contains $_.Name} |
Foreach {
#Stop Service
#Verify Service
#Restart Service
}

I'm not sure how you want to handle this, but since arrays is tagged:
$services = #("ServiceName1","ServiceName2")
#Turn off Services
foreach($service in $services) {
stop-service $Service1 -force
$VerifyServiceStopped1 = Get-Service $Service1 | Where-Object {$_.status -eq "Stopped"} | select -last 1
if ($VerifyServiceStopped1) {
Write-Host $Service1' Stop = Pass (0)'
} else {
Write-Host $Service1' Stop = Fail (1000)'
Exit '1000'
}
}
"so depending on the code from Development I may need to stop 1 to 4 services"
If you cannot define the logic involved in that decision, how do you plan to automate it?

Just wanted to provide the entire code used in case someone else is looking for something similar - thanks to everyone for throwing ideas into the mix it was very helpful.
#Define Services
$Service1 = 'servicename'
$Service2 =
$Service3 =
$Service4 =
$Service5 =
$Service6 =
$Service7 =
$Service8 =
$Service9 =
$services = #(
$Service1,
$service2,
$service3,
$service4,
$Service5,
$Service6,
$Service7,
$Service8,
$Service9)
#Stop Services
Get-service |
Where { $Services -Contains $_.Name} |
Foreach {
$_ | stop-service
}
Set-Service |
Where { $Services -Contains $_.Name} |
Foreach {
$_ | -startuptype "Disabled"
}
#Verify Services
Get-service |
Where { $Services -Contains $_.Name} |
Foreach {
if ((Get-Service $_.Name).Status -eq "stopped") {Write-Host 'Service Start Pass (0)'} else {Write-Host 'Start Fail (1000)';Exit '1000'}
}
#Start Services
Set-Service |
Where { $Services -Contains $_.Name} |
Foreach {
$_ | -startuptype "Automatic"
}
Get-service |
Where { $Services -Contains $_.Name} |
Foreach {
$_ | start-service
}
#Verify Services
Get-service |
Where { $Services -Contains $_.Name} |
Foreach {
if ((Get-Service $_.Name).Status -eq "running") {Write-Host 'Service Start Pass (0)'} else {Write-Host 'Start Fail (2000)';Exit '2000'}
}
This allows me to have someone list any services they need to stop / start to deploy custom code out there - also then I will be moving this to stop executables in order to replace them and other files for verification. Sadly we are handed a collection of exe's and dll's to just hot swap for some deployments of software so this is why this was needed. I wanted a way of defining what I need to move in and out per deployment but didn't want to comment out lines throughout the script that I didn't need (i.e. only had two services so needed to comment out the others throughout the script).

Just access the status directly:
> (Get-Service -Name "Windows Time").Status -eq "Stopped"
$true
or
if ((Get-Service -Name "Windows Time").Status -eq "Stopped")
{
Write-Host "Yep..."
}
or if you want to be really terse, you can use the alias gsv:
> (gsv "Windows Time").Status -eq "Stopped"
$true
You could also make a function:
Function IsStopped ($name)
{
((Get-Service $name).Status -eq "Stopped")
}
if (IsStopped "Windows Time")
{
Write-Host "foo"
}

Related

PowerShell Get Windows OS Version Fast and Do Different Things

Is there a faster way to get a specific registry value from a list of servers? I'm selecting a text file of computers with different flavors of windows and getting the OS product name. I'm finding that it's taking a couple seconds per computer to retrieve.
Current script:
Clear-Host
# Prompt for file containing list of target
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$myDialog = New-Object System.Windows.Forms.OpenFileDialog
$myDialog.Title = "Select File of Target Systems"
$myDialog.InitialDirectory = $PSScriptRoot
$myDialog.Filter = "TXT (*.txt) | *.txt"
$result = $myDialog.ShowDialog()
If ($result -eq "OK") {
$Computers = Get-Content $myDialog.FileName
}
Else {
Write-Host "`nCancelled by User`n"
}
$Array = #()
# Loop Through Computers
ForEach ($Computer in $Computers) {
Write-Warning "Processing $Computer"
# Get Registry Values
Try {
$OSVersion = Invoke-Command -ComputerName $Computer -ScriptBlock { (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName }
# Create a custom object
$ComplexObject = New-Object PSCustomObject
$ComplexObject | Add-Member -MemberType NoteProperty -Name "Server name" -Value $Computer
$ComplexObject | Add-Member -MemberType NoteProperty -Name "OS Version" -Value $OSVersion
# Add custom object to our array
$Array += $ComplexObject
}
Catch {
$_.Exception.Message
Break
}
}
# Results
If ($Array) {
# Display results in new window
$Array | Out-GridView -Title "OS Version Results"
# Display results in PS console
$Array
}
My end goal later on in the script is to do different things based on the OS version so I want to separate them into independent lists:
If (We have Win2008 servers) {
"Do This"
}
If (We have Win2012R2 servers) {
"Do This"
}
Clear-Host
# Prompt for file containing list of target
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$myDialog = [System.Windows.Forms.OpenFileDialog]::new()
$myDialog.Title = "Select File of Target Systems"
$myDialog.InitialDirectory = $PSScriptRoot
$myDialog.Filter = "TXT (*.txt) | *.txt"
$result = $myDialog.ShowDialog()
If ($result -eq "OK") {
$Computers = Get-Content $myDialog.FileName
}
Else {
Write-Host "`nCancelled by User`n"
}
# Get Registry Values
$Array = Try {
Invoke-Command -ComputerName $Computers -ScriptBlock {
(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName
} -ErrorAction stop | Select-Object #{n="Server Name";e={$_.pscomputername}},
#{n="OS Version";e={$_}}
}
Catch {
write-warning $_.Exception.Message
break
}
# Results
If ($Array) {
# Display results in new window
$Array | Out-GridView -Title "OS Version Results"
# Display results in PS console
$Array
}
You can use Get-AdComputer like:
Get-ADComputer -Filter {(OperatingSystem -like "*windows*server*") -and (Enabled -eq "True")} -Properties OperatingSystem | Select -ExpandProperty OperatingSystem | ForEach {
If($_ -match "Windows Server 2008.*"){
# Server 2008
}
If($_ -match "Windows Server 2012.*"){
# Server 2012
}
# Add more like 2016,2019
}

Identifying 32/64bit Version via Powershell Script

I need to identify if the installed software (several times per host) is an 32bit or 64bit version. To do this I want to check the Execution Folder of the Service via powershell.
This is my first powershell script and I'm a bit lost. I would like to store the information of Get-WmiObject win32_service to a multidimensional array.
If I run the command selecting PathName, State and DisplayName the PathName will be shortened, for that I run this command several times. But don't know how to get is in the right fields of the array or get the right fields in my foreach
Here is what I got so far:
`$ServiceArray = #()
$ServiceArray[] = Get-WmiObject win32_service | ?{$_.Name -like 'foo_*'} |
Select PathName
$ServiceArray[][] = Get-WmiObject win32_service | ?{$_.Name -like 'foo_*'} |
Select State
$ServiceArray[][][] = Get-WmiObject win32_service | ?{$_.Name -like 'foo_*'}
| Select DisplayName
foreach($array in $ServiceArray[])
{
if ($array.Contains(\bin\test\win64\test.exe)
{
$ServiceArray[][][][] = "win64"
}
else
{
$ServiceArray[][][][] = "win32"
} `
I know that it does not work this way, but I don't know how it works correct, either.
You can select multiple properties in the same statement with Select-Object:
$ServiceArray = Get-WmiObject Win32_Service |
Where-Object {$_.Name -like 'foo_*'} |
Select PathName,State,DisplayName
You can also use Select-Object with a calculated property to add the bitness based on the PathName argument if needed:
$ServiceArray = Get-WmiObject Win32_Service |
Where-Object {$_.Name -like 'foo_*'} |
Select PathName,State,DisplayName,#{Name='Bitness';Expression={if($_.PathName -like "*Win64*"){"Win64"}else{"Win32"}}}
You can select multiple properties. You were almost there. Edited your code a little.
$ServiceArray = #()
$ServiceArray = Get-WmiObject win32_service | ?{$_.Name -like 'foo_*'} | Select PathName,State,DisplayName,Architecture
foreach($Element in $ServiceArray)
{
if ($Element.PathName.Contains('\bin\test\win64\test.exe'))
{
$Element.Architecture = 'win64'
}
else
{
$Element.Architecture = 'win32'
}
$Element
}

Calling an Array in PowerShell

I have a string array $Exclude which I want to put into a filter in a Get-WMIObject cmdlet. I have added it at the end but it does not work.
How can I filter out the services that are listed in that array?
$ServicesToExclude = "RemoteRegistry,CpqNicMgmt"
$Exclude = $ServicesToExclude.split(",")
$Services = Get-WmiObject -Class Win32_Service -Filter {State != 'Running' and StartMode = 'Auto' and Name -ne $Exclude}
$Result = foreach ($Service in $Services.Name)
{
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$Service" |
Where-Object {$_.Start -eq 2 -and $_.DelayedAutoStart -ne 1}|
Select-Object -Property #{label='ServiceName';expression={$_.PSChildName}} |
get-Service
}
If ($Result.count -gt 0){
$Displayname = $Result.displayname
[string] $Line = "`n-----------------------------------------"
$Api.LogScriptEvent( 'Stopped_Auto_Services.ps1',1234,4,"`nStopped Automatic Services$Line `n$($Displayname)")
Filtering an array out of a list is not done on the WMI side. Instead, you should use Where-Object to filter out those services which name is contained in $Exclude.
$Services = Get-WmiObject -Class Win32_Service -Filter {State != 'Running' and StartMode = 'Auto'} |
Where-Object {$Exclude -notcontains $_.Name}
WMI queries do not work well with arrays and need to be done a different way. If you want to keep the filtering on the server side, you can do some work prior to running the command by creating a filter string as shown here:
$Exclude = "RemoteRegistry","CpqNicMgmt"
$StringBuilder = New-Object System.Text.StringBuilder
[void]$StringBuilder.Append("State != 'Running' AND StartMode = 'Auto' AND ")
[void]$StringBuilder.Append("($(($Exclude -replace '^(.*)$','Name != "$1"') -join ' AND '))")
$Query = $StringBuilder.ToString()
$Services = Get-WmiObject -Class Win32_Service -Filter $Query
There may be better ways to accomplish this, but this was the first thing that I could think of to accomplish the goal of your question.

Ping a list of host names and output the results to a csv in powershell

I have a large list of hostnames I need to ping to see if they are up or down. I'm not really that great at scripting but I managed to figure this much out:
$names = Get-content "hnames.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
Write-Host "$name is up" -ForegroundColor Green
}
else{
Write-Host "$name is down" -ForegroundColor Red
}
}
This gets me what I need but i now need to write out these results to a csv file and i have no idea how to do that.
Please Help!
You can use the following code instead (I simply altered the write-host calls to CSV formatting) and execute it with "PowerShell.exe script.ps > output.csv"
Note that you must execute it from the folder that contains hnames.txt, or simply change the "hnames.txt" to a full path.
$names = Get-content "hnames.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
Write-Host "$name,up"
}
else{
Write-Host "$name,down"
}
}
P.S. You can also use the Out-File Cmdlet to create the CSV file
I am a complete newbie to Powershell, so I took this on as a learning task, as I needed a quick and simple way to check a list of PC's for up/down status. These tweaks were needed to get it to output cleanly to the screen and to a txt file
$Output= #()
$names = Get-content "hnames.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
$Output+= "$name,up"
Write-Host "$Name,up"
}
else{
$Output+= "$name,down"
Write-Host "$Name,down"
}
}
$Output | Out-file "C:\support\result.csv"
$Output= #()
$names = Get-Content ".\input\Servers.txt"
foreach ($name in $names){
if (Test-Connection -Delay 15 -ComputerName $name -Count 1 -ErrorAction SilentlyContinue -quiet){
$Output+= "$name,up"
Write-Host "$Name,up" -ForegroundColor Green
}
else{
$Output+= "$name,down"
Write-Host "$Name,down" -ForegroundColor Red
}
}
$Output | Out-file ".\output\result.csv"
This is a tad cleaner, and includes the original foreground options but, BTW, the 'delay' switch seems to be ignored -PB
I would do it this way. Using a list of computers and -asjob works very well. The Responsetime property (confusingly the header is "Time(ms)") will be non-null if the host is up.
$names = Get-content hnames.txt
test-connection $names -asjob -count 1 | receive-job -wait -auto
Source Destination IPV4Address IPV6Address Bytes Time(ms)
------ ----------- ----------- ----------- ----- --------
COMP001 yahoo.com 74.6.231.21 32 39
COMP001 microsoft.com 40.113.200.201 32
Lately I do it this way. It requires threadjobs installed in powershell 5.1. Or just use get-port. I stick it in a mymod\mymod.psm1 module file somewhere in $env:psmodulepath. I can check a classroom in under 10 seconds.
function get-pport { # multi-threaded
param($list)
$list |
% { $_ | start-threadjob { get-port $input } -throttlelimit 20 } |
receive-job -wait -auto
}
function Get-Port {
Param (
[parameter(ValueFromPipeline)]
[string[]]$Hostname='yahoo.com'
)
begin {
$ports = 22,5988,3389,5985
$ping = New-Object System.Net.Networkinformation.ping
$Timeout = 200 # ms
}
process {
$hostname | foreach {
$openPorts = #()
foreach ($port in $ports) {
$client = New-Object System.Net.Sockets.TcpClient
$beginConnect = $client.BeginConnect($_,$port,$null,$null)
Start-Sleep -Milli $TimeOut
if($client.Connected) { $openPorts += $port }
$client.Close()
}
$result = $Ping.Send($_, $timeout)
if (! $result) { write-error "hostname $_ not found" }
$pingstatus = ($result.status -eq 'Success')
New-Object -typename PSObject -Property #{
HostName = $_
Port = $openPorts
Ping = $pingstatus
} | select hostname,port,ping
} # end foreach
} # end process
}
Example:
$avid = cat afile.txt
pport $avid
HostName Port Ping
-------- ---- ----
A006 {3389, 5985} True
A011 {3389, 5985} True
A015 {3389} True

Looking to speed up this PowerShell Function

I'm running the following code to pull data from SCOM 2012 and using an exported spreadsheet from SCCM 2012, output servers which are pending reboot along with their SCCM maintenance window for automated scheduled reboots.
The code takes around 5-8 minutes to run and I was wondering if there was any way to speed up the process. The code running under Begin Loop is the bottle neck.
Function Generate-RebootData{
IF(Get-Command Get-SCOMAlert -ErrorAction SilentlyContinue){}ELSE{Import-Module OperationsManager}
"Get Pend reboot servers from prod"
New-SCOMManagementGroupConnection -ComputerName ProdSrv
$AlertData = get-SCOMAlert -Criteria `
"Severity = 1 AND ResolutionState < 254 AND Name = 'Pending Reboot'" |
Select NetbiosComputerName
"Get Pend reboot servers from cert"
#For cert information
New-SCOMManagementGroupConnection -ComputerName CertSrv
$AlertData += Get-SCOMAlert -Criteria `
"Severity = 1 AND ResolutionState < 254 AND Name = 'Pending Reboot'" |
Select NetbiosComputerName
"Remove duplicates"
$AlertDataNoDupe = $AlertData | Sort NetbiosComputerName -Unique
"Create hash table"
$table = #{}
"Populate hash table"
Import-Csv D:\Scripts\servers2.csv | ForEach-Object {
$table[$_.Computername] = $_.'Collection Name'}
"Create final object"
$result = #{}
"Begin Loop"
$result = $AlertDataNoDupe | ForEach-Object { [PSCustomObject] #{
Server=$_.NetbiosComputerName
MaintenanceWindow=IF($table[$_.NetbiosComputerName]){$table[$_.NetbiosComputerName]}
ELSE{"Not found!"}
PingCheck=IF(Test-Connection -Count 1 $_.NetbiosComputerName -Quiet -EA SilentlyContinue)
{"Alive"}
ELSE{"Dead"}
LastReboot=Try{
$operatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName `
$_.NetbiosComputerName -ErrorAction Stop
[Management.ManagementDateTimeConverter]::ToDateTime(`
$operatingSystem.LastBootUpTime)}
Catch{"Access Denied!"}
} }
}
You should perform the PingCheck first, and only if that succeeds move on with the Get-WmiObject call - there's no need to contact a machine if you've just determined that it's "dead".
...
$result = $AlertDataNoDupe | ForEach-Object {
# Create hashtable
$Properties = #{
Server = $_.NetbiosComputerName
MaintenanceWindow = if($table[$_.NetbiosComputerName]){
= $_.NetbiosComputerName
} else {
'Not found!'
}
}
# Perform ping check, keep as boolean
$Properties['PingCheck'] = Test-Connection -Count 1 $_.NetbiosComputerName -Quiet -EA SilentlyContinue
$Properties['LastReboot'] = if($Properties['PingCheck'])
{
try
{
# Server seems to be online
$operatingSystem = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $_.NetbiosComputerName -ErrorAction Stop
[Management.ManagementDateTimeConverter]::ToDateTime($operatingSystem.LastBootUpTime)
}
catch
{
'Access Denied!'
}
}
else
{
# If server doesn't respond, declare it offline
'Computer offline!'
}
# create the object
New-Object -TypeName psobject -Property $Properties
}

Resources