Powershell script to Stop and start windows Service using S.no - powershell

I have a listed my Windows Service Names in the Text file and I have added the code to display it by adding the S.no preceding to the Names,
Listofservices.txt contains
Print Spooler
Windows Update
Remote Desktop Services
Get-Content 'C:\Services\Listofservices.txt' | ForEach-Object { $i = 1 } { "$i.$_" ; $i++ }
Result
Print Spooler
Windows Update
Remote Desktop Services
now I would like to stop, start the services by entering only the S.no and I don't want to type the exact full name
$userinput = read-host -prompt "Enter the S.no to Stop/Start"
Stop-Service -Name "$userinput" -Force -Confirm
say for example if i enter the number 1 the Print Spooler service will be stopped

Here is one way you could do it following the code you already have, but as stated in comments, this is much simpler to do with Out-GridView -PassThru. Also do note, for these services, the PowerShell process will most likely require to be elevated.
$file = Get-Content path\to\file.txt
$file | ForEach-Object { $i = 1 } { "$i.$_" ; $i++ }
$userinput = Read-Host "Enter the S.no to Stop/Start"
try {
$choice = $file[-1 + $userinput]
if(-not $choice) {
throw 'Invalid selection..'
}
$service = Get-Service $choice
if('Running' -eq $service.Status) {
$service.Stop()
"$($service.Name) has been stopped.."
return # end the script here
}
$service.Start()
"$($service.Name) has been started.."
}
catch {
if($_.Exception.InnerException.InnerException.NativeErrorCode -eq 5) {
return "Process needs to be elevated."
}
"An error ocurred: $_"
}

Related

Script won't run in Switch menu

function Show-Menu { #Create the Show-Menu function
param ([string]$Title = 'Functions') #Sets title
Clear-Host
Write-Host "`t6: Reboot History." -foregroundcolor white
Write-Host "`tQ: Enter 'Q' to quit."
} #close of create show menu function
#Begin Main Menu
do
{
Show-Menu #Displays created menu above
$Selection = $(Write-Host "`tMake your selection: " -foregroundcolor Red -nonewline; Read-Host)
switch ($selection) #Begin switch selection
{
#===Reboot History===
'6' {
$Workstation = $(Write-Host "Workstation\IP Address" -nonewline -foregroundcolor DarkGreen) + $(Write-Host "(Use IP for remote users)?: " -NoNewline; Read-Host)
$DaysFromToday = Read-Host "How many days would you like to go back?"
$MaxEvents = Read-Host "How many events would you like to view?"
$EventList = Get-WinEvent -ComputerName $Workstation -FilterHashtable #{
Logname = 'system'
Id = '41', '1074', '1076', '6005', '6006', '6008', '6009', '6013'
StartTime = (Get-Date).AddDays(-$DaysFromToday)
} -MaxEvents $MaxEvents -ErrorAction Stop
foreach ($Event in $EventList) {
if ($Event.Id -eq 1074) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Restart'
UserName = $Event.Properties.value[6]
}
}
if ($Event.Id -eq 41) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Unexpected'
UserName = ' '
}
}
}
pause
}
}
}
until ($selection -eq 'q') #End of main menu
Works perfectly fine if I remove the script from the switch and run it separately, but as soon as I call it from the switch it still asks for the workstation/IP, how many days, and max events, but just outputs nothing.
Here is what it looks like when it works:
How many days would you like to go back?: 90
How many events would you like to view?: 999
TimeStamp Event ShutdownType UserName
--------- ----- ------------ --------
12/23/2022 12:20:55 AM 1074 Restart Username
12/20/2022 1:00:01 AM 1074 Restart Username
12/17/2022 12:21:54 AM 1074 Restart Username
12/13/2022 8:57:40 AM 1074 Restart Username
This is what I get when I run it within the switch menu
Workstation\IP Address(Use IP for remote users)?: IP Address
How many days would you like to go back?: 90
How many events would you like to view?: 999
Press Enter to continue...:
I have tried just doing 1 day and 1 event, but same results. No errors or anything indicating a failure, so not sure how to troubleshoot this. I have had similar issues with switches in the past that were resolved with some researching into scopes, but I don't think this is the same case as it is all self contained within the switch itself.
I am at a loss, any ideas? As always, any insight into my script is greatly appreciated, even if it doesn't resolve the problem at hand.
JosefZ has provided the crucial pointer:
force synchronous to-display output with, such as with Out-Host
if you neglect to do so, the pause statement will - surprisingly - execute before the [pscustomobject] instances emitted by the foreach statement, due to the asynchronous behavior of the implicitly applied Format-Table formatting - see this answer for details.
Here's a simplified example:
switch ('foo') {
default {
# Wrap the `foreach` statement in . { ... },
# so its output can be piped to Out-Host.
. {
foreach ($i in 1..3) {
[pscustomobject] #{ prop = $i }
}
} |
Out-Host # Without this, "pause" will run FIRST.
pause
}
}
Note:
For Out-Host to format all output together it must receive all output from the foreach loop as part of a single pipeline.
Since foreach is a language statement (rather than a command, such as the related ForEach-Object cmdlet) that therefore cannot directly be used at the start of a pipeline, the above wraps it in a script block ({ ... }) that is invoked via ., the dot-sourcing operator, which executes the script block directly in the caller's context and streams the output to the pipeline.
This limitation may be surprising, but is rooted in the fundamentals of PowerShell's grammar - see GitHub issue #10967.
An all-pipeline alternative that doesn't require the . { ... } workaround would be:
1..3 |
ForEach-Object {
[pscustomobject] #{ prop = $_ } # Note the automatic $_ var.
} |
Out-Host

Getting quick Server Ping Results in Powershell

I have written below PowerShell script to get the ping results of server and it also generates the log for the same. My script works fine, also generates log but it take so much time as there are many servers. Is there any way I can quickly get the results. I have tried start-job but that generates error.
$logpath = $logpath +"\" +$Logname
Function Log($txt)
{
if (! (Test-Path $logpath)) {
New-Item $logpath |Out-Null
}
$date = Get-Date -Format "dd_MMM_yyyy_hh:mm:ss - "
$txt1 = ($date + $txt)
Add-Content "$logpath" "$txt1"
Add-Content "$logpath" " "
}
$ServerDetails=import-csv $Servercsv
foreach($servertest in $ServerDetails)
{
if((Test-Connection -ComputerName $servertest.servers -Count 2))
{
Log("'" + $servertest.servers + "' Successfully started operation")
Write-Host "Started Operation Successfully"
}
if(!(Test-Connection -ComputerName $servertest.servers -Count 2))
{
Log("'" + $servertest.servers + "'Servers are not pinging")
Write-Host "Servers are not pinging"
}
}
Assumed your "log" function works as expected you could make the rest of your code a little more efficient:
$ServerList = import-csv $Servercsv
foreach ($ComputerName in $ServerList) {
$Connection = Test-Connection -ComputerName $ComputerName.servers -Count 1 -Quiet
if ($Connection) {
Log("'" + $ComputerName.servers + "' Successfully started operation")
Write-Host "Started Operation Successfully"
}
Else {
Log("'" + $ComputerName.servers + "'Servers are not pinging")
Write-Host "Servers are not pinging"
}
}
For the speed effort for pinging, try this one out and compare speeds relative to how you are doing/getting from yours.
Final Super-Fast Ping Command
function Test-OnlineFast
{
param
(
# make parameter pipeline-aware
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]
$ComputerName,
$TimeoutMillisec = 1000
)
begin
{
# use this to collect computer names that were sent via pipeline
[Collections.ArrayList]$bucket = #()
# hash table with error code to text translation
$StatusCode_ReturnValue =
#{
0='Success'
11001='Buffer Too Small'
11002='Destination Net Unreachable'
11003='Destination Host Unreachable'
11004='Destination Protocol Unreachable'
11005='Destination Port Unreachable'
11006='No Resources'
11007='Bad Option'
11008='Hardware Error'
11009='Packet Too Big'
11010='Request Timed Out'
11011='Bad Request'
11012='Bad Route'
11013='TimeToLive Expired Transit'
11014='TimeToLive Expired Reassembly'
11015='Parameter Problem'
11016='Source Quench'
11017='Option Too Big'
11018='Bad Destination'
11032='Negotiating IPSEC'
11050='General Failure'
}
# hash table with calculated property that translates
# numeric return value into friendly text
$statusFriendlyText = #{
# name of column
Name = 'Status'
# code to calculate content of column
Expression = {
# take status code and use it as index into
# the hash table with friendly names
# make sure the key is of same data type (int)
$StatusCode_ReturnValue[([int]$_.StatusCode)]
}
}
# calculated property that returns $true when status -eq 0
$IsOnline = #{
Name = 'Online'
Expression = { $_.StatusCode -eq 0 }
}
# do DNS resolution when system responds to ping
$DNSName = #{
Name = 'DNSName'
Expression = { if ($_.StatusCode -eq 0) {
if ($_.Address -like '*.*.*.*')
{ [Net.DNS]::GetHostByAddress($_.Address).HostName }
else
{ [Net.DNS]::GetHostByName($_.Address).HostName }
}
}
}
}
process
{
# add each computer name to the bucket
# we either receive a string array via parameter, or
# the process block runs multiple times when computer
# names are piped
$ComputerName | ForEach-Object {
$null = $bucket.Add($_)
}
}
end
{
# convert list of computers into a WMI query string
$query = $bucket -join "' or Address='"
Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" |
Select-Object -Property Address, $IsOnline, $DNSName, $statusFriendlyText
}
}
Test-OnlineFast -ComputerName google.de, powershellmagazine.com, 10.10.10.200, 127.0.0.1
<#
# Results
Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
powershellmagazine.com True powershellmagazine.com Success
10.10.10.200 False Request Timed Out
#>
Test-Connection with an array of targetnames and the asjob parameter is fast (powershell 5.1). Unresponsive addresses have a null responsetime. Annoyingly, the property names don't match the column headings.
$list = 'microsoft.com','yahoo.com'
$results = test-connection $list -count 1 -asjob | receive-job -wait
$results | select address,responsetime
address responsetime
------- ------------
yahoo.com 39
microsoft.com

How do I add a string to a variable called from a CSV file before it is called in a function?

I am trying to ping a list of IPs, but I need to test variations of them for RACs and KVMs. I want to take the IP address (in the $ColumnHeader variable) and append "rac" to it before pinging it. I also will need to ping "arac", "raca", "kv", "akv", "kva", and "kvm", but I was just going to substitute those manually into the script after running it each time.
Param(
[Parameter(Mandatory=$true, position=0)][string]$csvfile
)
$ColumnHeader = "Name"
#$string = "rac"
#$subServer = "$($ColumnHeader)$($string)"
Write-Host "Reading file" $csvfile
$ipaddresses = Import-Csv $csvfile | Select-Object $ColumnHeader
Write-Host "Started Pinging.."
foreach ($ip in $ipaddresses) {
if (Test-Connection $ip.($ColumnHeader) -Count 1 -Quiet) {
Write-Host $ip.("Name") "Ping succeeded." -Foreground Green
} else {
Write-Host $ip.("Name") "Ping failed." -Foreground Red
}
}
Write-Host "Pinging Completed."
I've tried creating new variables $string and $subServer and using those in place of $ColumnHeader on line 14, but I get a "Cannot validate argument on parameter 'ComputerName'." error.
EDIT: I changed Line 7 from $subServer = $ColumnHeader + $string to $subServer = "$($ColumnHeader)$($string)", and I can successfully call $subServer on Line 14 in place of $ColumnHeader, but when I uncomment $string on Line 6, I get the aforementioned 'ComputerName' error again.

Including common code in header/footer with PowerShell

I have common functions and formats to most of my scripts. Each script brings up a window for me to paste workstations and it performs basic tasks like checking connectivity before proceeding. Generally, I copy and paste this code and modify the body. What I would like to do is include a header and footer, but I get "Missing closing '}' in statement block." errors. Example:
<# Begin Header #>
if($canceled) {
write-host "Operation canceled."
}
else {
if($computers.length -gt 0) {
[array]$computers = $computers.split("`n").trim()
# Loop through computers entered
foreach($pc in $computers) {
# Skip zero length lines for computers
if(($pc.length -eq $null) -OR ($pc.length -lt 1)) {
continue
}
else {
# Try to connect to the computer, otherwise error and continue
write-host "Connecting to: $pc$hr"
if(test-connection -computername $pc -count 1 -ea 0) {
<# End Header #>
Body of script
<# Begin Footer #>
}
else {
utc # Unable to contact
}
}
write-host "`n"
}
}
}
<# End Footer #>
Rather than copying/pasting each time, I would prefer to do this...
."c:\scripts\header.ps1"
-- code --
."c:\scripts\footer.ps1"
Is that even possible when the header ends with an open bracket? I do this in PHP but I can't figure out a work-around in PowerShell.
Your approach could be changed into storing a function in one file and your custom script that runs for-each server in another. You can store a scriptblock to a variable in PowerShell and pass that as a parameter to a function. You can use Invoke-Command -scriptblock $Variable to execute that code.
Write your function like this:
function runAgainstServerList {
param ( [ScriptBlock]$ScriptBlock)
if($canceled) {
write-host "Operation canceled."
}
else {
if($computers.length -gt 0) {
[array]$computers = $computers.split("`n").trim()
# Loop through computers entered
foreach($pc in $computers) {
# Skip zero length lines for computers
if(($pc.length -eq $null) -OR ($pc.length -lt 1)) {
continue
}
else {
# Try to connect to the computer, otherwise error and continue
write-host "Connecting to: $pc$hr"
if(test-connection -computername $pc -count 1 -ea 0) {
Invoke-Command -ScriptBlock $ScriptBlock
}
else {
utc # Unable to contact
}
}
write-host "`n"
}
}
}
}
Now save that off to your include file like 'myFunctions.ps1'
Then create your custom script that you want to run per server like this:
. myFunctions.ps1
[ScriptBlock]$ScriptBlockToPass = {
## Insert custom code here
}
runAgainstServerList $ScriptBlockToPass
To get you a step closer to what might be your end goal, You may want to append the -ComputerName "ComputerNameHere" argument to your invoke-command statement inside your included function. This would cause your script to be executed on the remote system instead of locally.

ForEach from a text file with my PS script

I was hoping someone would be able to help me add a foreach to my script that pulls hostnames from a text file. I know this is so basic, but I can't make it work. Thank you in advance.
Here is my script:
#Days passed from last SEP client update
$Error.Clear();
try {
$res=(Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Symantec\Symantec Endpoint Protection\AV" PatternFileDate).PatternFileDate
}
catch
{
Write-Host "ERROR: $($Error[0])";
exit 1;
}
if ($Error.Count -eq 0) {
$y1=[int]$res[0]+1970;
$m1=[int]$res[1]+1;
$d1=[int]$res[2];
$stat2 = [string](get-date -uformat "%m %d %Y")
$t2=$stat2.split(" ")
$m2=[int]$t2[0];
$d2=[int]$t2[1];
$y2=[int]$t2[2];
$diff=($y2-$y1)*365+($m2-$m1)*30+($d2-$d1);
write-host "Statistic: $diff";
write-host "Message: Last SEP client update date: $m1/$d1/$y1";
exit 0;
}
write-host "Message: Can't find ""HKLM:\SOFTWARE\Symantec\Symantec Endpoint Protection\AV\PatternFileDate"" registry value";
You are looking for the Get-Content cmdlet. Example:
Get-Content 'PATH_TO_YOUR_TEXT_FILE' | ForEach-Object {
$hostname = $_
}
The text file should look like this:
host1
host2
host3