I have a basic script, which will shutdown Windows services and generate a report about their shutdown processes. I also want to include two more columns into my output variable ($table), which will be timestamp values i.e. when shutdown tasks were launched and when they finished. I have no idea how to implement this into my report.
$processlist = #('SQLTELEMETRY$TESTDB', 'MSSQL$TESTDB', 'SQLWRITER')
$get = ''
$table = #{ }
$failed = 0
foreach ($proc in $processlist) {
stop-service -name $proc -force
}
#start-sleep -s 120
foreach ($proc in $processlist) {
$get = get-service $proc -Erroraction ignore
if ($get.Status -eq 'Running') {
$table += #{$proc = 'Running' }
}
else {
$table += #{$proc = 'Stopped' }
}
}
foreach ($value in $table.GetEnumerator()) {
if ($value.Value -eq 'Running') {
$failed += 1
}
}
if ($failed -gt 0) {
$err = 'FAILED'
}
else {
$err = 'SUCCESS'
}
$table.GetEnumerator() | Select-Object -Property Name, Value | export-csv appreport.csv -delimiter ";" -force -notypeinformation
(HTML part here...)
Instead of adding stuff into a Hashtable, I think it would be a lot easier to build an array of objects and write that as CSV file.
Something like this:
$serviceList = 'SQLTELEMETRY$TESTDB', 'MSSQL$TESTDB', 'SQLWRITER'
$maxAttempts = 10
# $result will become an array of PsCustomObjects you can easily pipe to Export-Csv
$result = foreach ($service in $serviceList) {
$shutStart = Get-Date
$svc = Get-Service -Name $service -ErrorAction SilentlyContinue
if ($svc) {
for ($attempt = 0; $attempt -lt $maxAttempts; $attempt++) {
$shutResult = 'Failed'
Start-Sleep -Milliseconds 100
$svc | Stop-Service -Force -ErrorAction SilentlyContinue
# test if the service has stopped. If so exit the loop
if (($svc | Get-Service).Status -eq 'Stopped') {
$shutResult = 'Success'
break
}
}
[PsCustomObject]#{
'ServiceName' = $svc.Name
'ServiceDisplayName' = $svc.DisplayName
'ShutDownStart' = $shutStart
'ShutDownEnd' = Get-Date
'Result' = $shutResult
}
}
else {
[PsCustomObject]#{
'ServiceName' = $service
'ServiceDisplayName' = ''
'ShutDownStart' = $shutStart
'ShutDownEnd' = Get-Date
'Result' = "Failed: Service '$service' could not be found."
}
}
}
# output on screen
$result
# output to CSV
$result | Export-Csv 'D:\appreport.csv' -Delimiter ";" -Force -NoTypeInformation
The output on screen will look like this:
ServiceName : SQLTELEMETRY$TESTDB
ServiceDisplayName :
ShutDownStart : 22-8-2019 16:47:40
ShutDownEnd : 22-8-2019 16:47:40
Result : Failed: Service 'SQLTELEMETRY$TESTDB' could not be found.
ServiceName : MSSQL$TESTDB
ServiceDisplayName :
ShutDownStart : 22-8-2019 16:47:40
ShutDownEnd : 22-8-2019 16:47:40
Result : Failed: Service 'MSSQL$TESTDB' could not be found.
ServiceName : SQLWRITER
ServiceDisplayName : SQL Server VSS Writer
ShutDownStart : 22-8-2019 16:47:38
ShutDownEnd : 22-8-2019 16:47:39
Result : Success
Hope that helps
I don't really know when you want to capture the time stamp for the services, but I suggest you take advantage of the below property and add it in the loop where you think its suitable.
(Get-Process -Name $proc).StartTime
Also you can use the below properties :
UserProcessorTime
TotalProcessTime
ExitTime
I hope this will help you to capture to time.
Related
I'm new with powershell and i would like to use a loop to ping several Printers on my network.
My problem is : once i'm in the loop of pinging , i can't go out of the loop ...
I tried several things from google but without success ( start-stop , Timer ) . Does anybody have any idea?
Here is the code :
$BtnStartPingClicked = {
if ($LblFileSelectPing.Text -eq "*.txt") {
Add-Type -AssemblyName PresentationCore,PresentationFramework
$ButtonType = [System.Windows.MessageBoxButton]::OK
$MessageIcon = [System.Windows.MessageBoxImage]::Error
$MessageBody = "Please select a list of printer first"
$MessageTitle = "Error"
$Result = [System.Windows.MessageBox]::Show($MessageBody,$MessageTitle,$ButtonType,$MessageIcon)
Write-Host "Your choice is $Result"
}
else {
do {
$IPList = Get-Content ($LblFileSelectPing.Text)
$snmp = New-Object -ComObject olePrn.OleSNMP
$ping = New-Object System.Net.NetworkInformation.Ping
$i = 11
$j = 1
foreach ($Printer in $IPList) {
try {
$result = $ping.Send($Printer)
} catch {
$result = $null
}
if ($result.Status -eq 'Success') {
$((Get-Variable -name ("GBMachine"+$j+"Ping")).value).Visible = $True
$j++
test-Connection -ComputerName $Printer -Count 1 -Quiet
$printerip = $result.Address.ToString()
# OPEN SNMP CONNECTION TO PRINTER
$snmp.open($Printer, 'public', 2, 3000)
# MODEL
try {
$model = $snmp.Get('.1.3.6.1.2.1.25.3.2.1.3.1')
} catch {
$model = $null
}
# Serial
try {
$serial = $snmp.Get('.1.3.6.1.4.1.1602.1.2.1.8.1.3.1.1').toupper()
} catch {
$Dns = $null
}
# progress
$TBMonitoringPing.SelectionColor = "green"
$TBMonitoringPing.AppendText("$Printer is Pinging")
$TBMonitoringPing.AppendText("`n")
$mac = (arp -a $Printer | Select-String '([0-9a-f]{2}-){5}[0-9a-f]{2}').Matches.Value
# OPEN SNMP CONNECTION TO PRINTER
$((Get-Variable -name ('LblMach' + $i)).value).Text = "IP : $Printerip"
$i++
$((Get-Variable -name ('LblMach' + $i)).value).Text = "Model : $Model"
$i++
$((Get-Variable -name ('LblMach' + $i)).value).Text = "MAC : $mac"
$i++
$((Get-Variable -name ('LblMach' + $i)).value).Text = "Serial : $serial"
$TBAnswerMachine.AppendText("$Model")
$TBAnswerMachine.AppendText("`n")
$TBAnswerMachine.AppendText("$Printer - $Serial")
$TBAnswerMachine.AppendText("`n")
$TBAnswerMachine.AppendText("$Mac")
$TBAnswerMachine.AppendText("`n")
$TBAnswerMachine.AppendText("`n")
Get-Content ($LblFileSelectPing.Text) | Where-Object {$_ -notmatch $Printer} | Set-Content ("C:\_canonsoftware\out.txt")
$i = $i+7
$snmp.Close()
Start-Sleep -milliseconds 1000 # Take a breather!
}
else {
$TBMonitoringPing.selectioncolor = "red"
$TBMonitoringPing.AppendText("$Printer not pinging")
$TBMonitoringPing.AppendText("`n")
Start-Sleep -milliseconds 1000 # Take a breather!
}
}
$LblFileSelectPing.Text = "C:\_canonsoftware\out.txt"
} until($infinity)
}
}
thanks for your answers...
1 - part of my object are indeed not declared in the code because they are in my other PS1 file....
2 - I do a do until infinity because i don't want to stop the code before i decide it...
3 - I didn't explain my problem correctly ( excuse my poor english ) ... i would like to be able to go out of the loop do until at the moment i click on a stop button ... but apprently the windows doens't respond while in the loop ... i have to stop the script with powershell ... which is annoying because i'd like to make an executable with it ... and not have to go out of my program ...
thank you for your ideas
I'm trying to run on a list of processes and if found put the process name in hashtable with value True and if not False, the True part works just fine I got an issue if the process is not found it won't pair the searched process name with Flase just be blank
CODE
$processStatus = $null
$processStatus = #{}
$processName = "firefox", "discord", "fgdfg"
[System.Diagnostics.Process[]]$processList = Get-Process $processName -ErrorAction SilentlyContinue |
Sort-Object -Property name -Unique
ForEach ($process in $processList) {
if($process -ne $null) {
$processStatus.Add($process.ProcessName,"True")
} else {
$processStatus.Add($process,"Flase")
}
}
Result
Name Value
---- -----
Discord True
firefox True
Expected result
Name Value
---- -----
Discord True
firefox True
fgdfg False
Your quite close. You just need loop through the process names and run Get-Process against each process name. Then check if the process exists before setting the status in the hash table.
$processNames = "firefox", "discord", "fgdfg"
$processStatus = #{}
foreach ($processname in $processNames) {
$processes = Get-Process -Name $processName -ErrorAction SilentlyContinue
if ($null -ne $processes) {
$processStatus[$processName] = $true
}
else {
$processStatus[$processName] = $false
}
}
Also make sure to put $null on the left side of the operator when doing comparisons. If your using VSCode, PSScriptAnalyzer will give a PSPossibleIncorrectComparisonWithNull warning if you don't do this. Have a look at the documentation for more information as to why this is recommended.
We could also use the .NET method System.Diagnostics.Process.GetProcessByName to get the processes that share the same name. This method returns System.Diagnostics.Process[], so we can simply check if the count is greater than 0.
$processNames = "firefox", "discord", "fgdfg", "notepad"
$processStatus = #{}
foreach ($processname in $processNames) {
$processes = [System.Diagnostics.Process]::GetProcessesByName($processName)
if ($processes.Count -gt 0) {
$processStatus[$processName] = $true
}
else {
$processStatus[$processName] = $false
}
}
Execute Get-Process against each individual process name instead:
$processStatus = #{}
$processName = "firefox", "discord", "fgdfg"
$processName |ForEach-Object {
$processStatus[$_] = #(Get-Process -Name $_ -ErrorAction SilentlyContinue).Count -gt 0
}
If you want a list of status objects instead, you could also use Select-Object:
$processNames |Select-Object #{Name='Name';Expression={$_}},#{Name='Status';Expression={#(Get-Process -Name $_ -ErrorAction SilentlyContinue).Count -gt 0}}
The goal : get all logged in / logged out users from the system.
Those users who logged in / logged out by using remote desktop connection.
My script :
Param(
[array]$ServersToQuery = (hostname),
[datetime]$StartTime = "January 1, 1970"
)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = $StartTime
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
[array]$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "."
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan
#End
I really do not understand ps1 scripts. I've found this script but i want to use it for my purposes.
When i try to execute it with c# :
Scenario 1 :
string scriptText = "C:\\MyPath\\script.ps1";
try
{
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
Collection<PSObject> results = pipeline.Invoke();
It throws an error :
ps1 is not digitally signed.
Second scenario :
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(str_Path);
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
if (PowerShellInstance.Streams.Error.Count > 0)
{
// error records were written to the error stream.
// do something with the items found.
}
}
Streams are always empty. Actually it always count 0 rows.
Any idea/suggestion how to get this done ?
I used Write-Output instead of Write-Host into the end of the script without generating csv file. Credits to this.
This only prints the last server in the list, I'm looking to get all servers and print to screen
$machines = (Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach($machine in $machines){
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if($machinelist.InMaintenanceMode -eq $true){
$status = "$machine is in maintenance mode"
}else {
$status = "$machine is not in maintenance mode"
}
}
Write-Host $status
Here is a more PowerShell-like approach (not tested):
Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | ForEach-Object {
$machineName = $_.DNSName
[PSCustomObject] #{
"MachineName" = $machineName
"MaintenanceMode" = (Get-BrokerMachine -HostedMachineName $machine).InMaintenanceMode
}
} | Export-Csv "C:\whatever\results.csv" -NoTypeInformation
$Status is constantly being overwritten by the current machine in your list.
You're looking for:
$Status+=
As opposed to:
$Status=
You'll also want to explicitly state that $Status will be an array at the beginning like so:
$Status=#()
Or when you create the variable and omit the line at the beginning.
[array]$Status +=
Otherwise, you'll get results that run together as it will be treated as a [String]
another funky mode :
function get-BrokerMachineMode
{
param (
[Parameter(Mandatory = $true)]
[string[]]$machines
)
begin
{
$ErrorActionPreference = 'Stop'
Add-Type -Language CSharp #"
public class BrokenBroker {
qpublic System.String MachineName;
public System.String MaintenanceMode;
public BrokenBroker (string MachineName, string MaintenanceMode)
{
this.MachineName = MachineName;
this.MaintenanceMode = IsInMaintenanceMode;
}
}
"#
$status = #()
Write-Verbose "Created objects..."
}
process
{
try
{
$machines = (Get-BrokerMachine -AdminAddress $adminaddress `
-DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach ($machine in $machines)
{
Write-Verbose "Checking machine: $machine"
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if ($machinelist.InMaintenanceMode -eq $true)
{
$status += New-Object BrokenBroker($machine, $true)
}
else
{
$status += New-Object BrokenBroker($machine, $false)
}
}
}
catch
{
Write-Error $error[0].Exception.Message
}
$status
}
end
{
Write-Verbose "Done"
}
}
this is a function you just must to load then you can launch it just by using this command:
$computers = get-content = {PATH TO TXT FILE}
$list = get-BrokerMachineMode -machines $computers -Verbose
Edit - I should preface this by saying I'm very new to powershell. This is one of the first "complicated" scripts i've tried to write.
I'm trying to use workflow/parallel to run a script against two lists simultaneously. after I get it working, the goal will be to add multiple lists, to cut down on the time it takes to process each one.
The script by itself works great. Adding the workflow returns nothing. :/
$Global:ctxsession = Read-Host -Prompt 'Input the Username'
$serverlist1 = get-content .\serverlist1.txt
$serverlist2 = get-content .\serverlist2.txt
function citrixlist {
#([string]$Servers)
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$servers
)
foreach ($server in $servers) {
#ping first
if (Test-Connection -count 1 -computer $server -quiet) {
#temporary write-host to see where it hangs (might leave in permanently)
Write-Host $server
#check for sessions
$sessions = qwinsta /server $server| ?{ $_ -notmatch '^ SESSIONNAME' } | %{
#separating into objects
$item = "" | Select "Active", "SessionName", "Username", "Id", "State", "Type", "Device"
$item.Active = $_.Substring(0,1) -match '>'
$item.SessionName = $_.Substring(1,18).Trim()
$item.Username = $_.Substring(19,20).Trim()
$item.Id = $_.Substring(39,9).Trim()
$item.State = $_.Substring(48,8).Trim()
$item.Type = $_.Substring(56,12).Trim()
$item.Device = $_.Substring(68).Trim()
$item
}
foreach ($session in $sessions){
#match for session name entered
if ($session.Username -match $ctxsession){
Write-Host 'Found' $session.Username 'on' $server 'with Session ID' $session.Id $session.State
#Kill session with ID and Server, with verbose switch
#rwinsta $session.Id /server:$server /v
}
}
}
}
}
workflow get-citrixlist1 {
citrixlist -servers $serverlist1
}
workflow get-citrixlist2 {
citrixlist -servers $serverlist2
}
workflow get-citrixkill {
parallel {
get-citrixlist1
get-citrixlist2
}
}