run and handle output from ps1 script - powershell

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.

Related

Timestamp of finished Powershell task

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.

Only prints the last server in the list, I want all servers

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

Powershell workflow/parallel citrix sessions

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
}
}

Powershell : How to get Antivirus product details

We have over 1500 servers. Windows 2003, 2008 and 2012. I have to gather the details of antivirus(Product Name & Version) on these servers.
There could be multiple antivirus product.
I am not sure powershell script will work on 2003 server.
So, far i tried below scripts but not got useful information.
$av = get-wmiobject -class "Win32_Product" -namespace "root\cimv2" `
-computername "." -filter "Name like '%antivirus%'"
Below script is working fine on client operating system.
$wmiQuery = "SELECT * FROM AntiVirusProduct"
$AntivirusProduct = Get-WmiObject -Namespace "root\SecurityCenter2" -Query $wmiQuery #psboundparameters # -ErrorVariable myError -ErrorAction 'SilentlyContinue'
Write-host $AntivirusProduct.displayName
Can anybody advise me on this?
I am trying to get the details of antivirus(Product & Version)
What do i need to do for win server 2003?
You were on the right path, the following Powershell script works.
function Get-AntiVirusProduct {
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('name')]
$computername=$env:computername
)
#$AntivirusProducts = Get-WmiObject -Namespace "root\SecurityCenter2" -Query $wmiQuery #psboundparameters # -ErrorVariable myError -ErrorAction 'SilentlyContinue' # did not work
$AntiVirusProducts = Get-WmiObject -Namespace "root\SecurityCenter2" -Class AntiVirusProduct -ComputerName $computername
$ret = #()
foreach($AntiVirusProduct in $AntiVirusProducts){
#Switch to determine the status of antivirus definitions and real-time protection.
#The values in this switch-statement are retrieved from the following website: http://community.kaseya.com/resources/m/knowexch/1020.aspx
switch ($AntiVirusProduct.productState) {
"262144" {$defstatus = "Up to date" ;$rtstatus = "Disabled"}
"262160" {$defstatus = "Out of date" ;$rtstatus = "Disabled"}
"266240" {$defstatus = "Up to date" ;$rtstatus = "Enabled"}
"266256" {$defstatus = "Out of date" ;$rtstatus = "Enabled"}
"393216" {$defstatus = "Up to date" ;$rtstatus = "Disabled"}
"393232" {$defstatus = "Out of date" ;$rtstatus = "Disabled"}
"393488" {$defstatus = "Out of date" ;$rtstatus = "Disabled"}
"397312" {$defstatus = "Up to date" ;$rtstatus = "Enabled"}
"397328" {$defstatus = "Out of date" ;$rtstatus = "Enabled"}
"397584" {$defstatus = "Out of date" ;$rtstatus = "Enabled"}
default {$defstatus = "Unknown" ;$rtstatus = "Unknown"}
}
#Create hash-table for each computer
$ht = #{}
$ht.Computername = $computername
$ht.Name = $AntiVirusProduct.displayName
$ht.'Product GUID' = $AntiVirusProduct.instanceGuid
$ht.'Product Executable' = $AntiVirusProduct.pathToSignedProductExe
$ht.'Reporting Exe' = $AntiVirusProduct.pathToSignedReportingExe
$ht.'Definition Status' = $defstatus
$ht.'Real-time Protection Status' = $rtstatus
#Create a new object for each computer
$ret += New-Object -TypeName PSObject -Property $ht
}
Return $ret
}
Get-AntiVirusProduct
Output:
Product GUID : {B0D0C4F4-7F0B-0434-B825-1213C45DAE01}
Name : CylancePROTECT
Real-time Protection Status : Enabled
Computername : HOSTNAME
Product Executable : C:\Program Files\Cylance\Desktop\CylanceSvc.exe
Reporting Exe : C:\Program Files\Cylance\Desktop\CylanceSvc.exe
Definition Status : Up to date
Product GUID : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
Name : Windows Defender
Real-time Protection Status : Unknown
Computername : HOSTNAME
Product Executable : windowsdefender://
Reporting Exe : %ProgramFiles%\Windows Defender\MsMpeng.exe
Definition Status : Unknown
Instead of relying on running processes, you could query the registry :
$computerList = "localhost", "localhost"
$filter = "antivirus"
$results = #()
foreach($computerName in $computerList) {
$hive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $computerName)
$regPathList = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
foreach($regPath in $regPathList) {
if($key = $hive.OpenSubKey($regPath)) {
if($subkeyNames = $key.GetSubKeyNames()) {
foreach($subkeyName in $subkeyNames) {
$productKey = $key.OpenSubKey($subkeyName)
$productName = $productKey.GetValue("DisplayName")
$productVersion = $productKey.GetValue("DisplayVersion")
$productComments = $productKey.GetValue("Comments")
if(($productName -match $filter) -or ($productComments -match $filter)) {
$resultObj = [PSCustomObject]#{
Host = $computerName
Product = $productName
Version = $productVersion
Comments = $productComments
}
$results += $resultObj
}
}
}
}
$key.Close()
}
}
$results | ft -au
Example output :
Host Product Version Comments
---- ------- ------- --------
localhost Avast Free Antivirus 10.4.2233
localhost Avast Free Antivirus 10.4.2233
Would this work for you? It's written in PowerShell v2, so if you have that installed on your 2003 servers, it will run on all the servers. This code will give you a CSV of this data from whichever machines run the script that have a service with the description including the word "virus" (which I thought better than "antivirus" because some services use "anti-virus" instead). If they all have access to a shared resource, you can prepend that shared resource directory to the $Filename variable and it will name each report starting with that computer's name and dump your reports there.
invoke-command -computername Server01, Server02 -filepath c:\Scripts\get_av_info.ps1
Assuming the script is saved as c:\Scripts\get_av_info.ps1, that should run it on whatever machines you specify, or if you have a CSV of all the machines you want to run the script, ForEach it. I didn't try this, so I can't verify the remote invoking.
$Date = (Get-Date).ToString('yyyy-MM-dd')
$localhost = $env:computername
$Filename = "C:\" + $localhost + "_" + $Date + "_AV_FileInfo.csv"
$AV = get-process | ?{$_.Description -like "*virus*"}
$Process = ForEach($a in $AV){
$ID = $($a.Id)
get-process -Id $ID -FileVersionInfo
}
$Process | select "CompanyName","FileBuildPart","FileDescription","FileName","FileVersion","ProductName","ProductPrivatePart","ProductVersion","SpecialBuild" | Export-Csv $Filename -NoTypeInformation
There are a LOT of options, I just picked ones I thought you'd want. You could probably also combine the reports to one by adding a shared resource to the Filename and having it -Append, but you would run the risk of multiple servers trying to write to the file at the same time and failing to report at all.
You'll need to refine your results, of course. If you don't change anything, any machine where you run this will just drop a CSV called "COMPUTERNAME_DATE_AV_FileInfo.csv" at the root of it's C:\ drive.

sql failed jobs export to csv with powershell

I am battling with a PowerShell script that captures all SQL Failed jobs for the past day and exports it to .CSV
Please view code below.
param (
#[string]$serverInstance = '03RNB-VSQLPRD4\SQLPRD04
)
begin {
[void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
}
process {
try {
Write-Verbose "List failed SQL Server jobs using SMO..."
$serverInstance = Get-Content "C:\MountSpaceCollector\SQLJobFailures\servers2.txt";
$server = new-object Microsoft.SqlServer.Management.Smo.server $serverInstance
$results = #()
$reasons = #()
$jobs = $server.jobserver.jobs | where-object {$_.isenabled}
# Process all SQL Agent Jobs looking for failed jobs based on the last run outcome
foreach ($job in $jobs) {
[int]$outcome = 0
[string]$reason = ""
# Did the job fail completely?
if ($job.LastRunOutcome -eq "Failed") {
$outcome++
$reasons += "Job failed: " + $job.name + " Result: " + $job.LastRunOutcome
# Did any of the steps fail?
foreach ($jobStep in $job.jobsteps) {
if ($jobStep.LastRunOutcome -ne "Succeeded") {
$outcome++
$reasons += "Step failed: " + $jobStep.name + " Result: " + $jobStep.LastRunOutcome
}
}
}
if ($outcome -gt 0) {
$jobFailure = New-Object -TypeName PSObject -Property #{
Name = $job.name
LastRunDate = $job.lastrundate
LastRunOutcome = $reasons
}
$results += $jobFailure
}
}
Write-Output $results | Export-CSV -Path 'C:\MountSpaceCollector\SQLJobFailures\SQLJobFailures.csv' -Delimiter '|'
}
catch [Exception] {
Write-Error $Error[0]
$err = $_.Exception
while ( $err.InnerException ) {
$err = $err.InnerException
Write-Output $err.Message
Write-Output $results
}
}
}
But It Exports all except the last field (LastRunOutcome). It only displays "System.Object[]"?
Can anyone please assist with this as I do not know what I am doing wrong?
It's because $Reasons is an object, more specifically an array. You need to format the reasons differently, as a string for instance, to be able to have it appear in the CSV normally.
Perhaps you meant to use the string $Reason that you declare, but don't use?