I have a functional PowerShell script that I'm using to capture user logons and logoffs for single local machine. The script works fine, but I'm having a difficult time trying to pull the last 24 hours from current date/time. I have $Date = [DateTime]::Now.AddDays(-1) at the top of my script, but it appears to be getting ignored.
Can anyone tell me what I'm missing?
powershell
$Date = [DateTime]::Now.AddDays(-1)
$Date.tostring("MM-dd-yyyy_HH,mm,ss")
$UserProperty = #{n="user";e={(New-Object System.Security.Principal.SecurityIdentifier $_.ReplacementStrings[1]).Translate([System.Security.Principal.NTAccount])}}
$TypeProperty = #{n="Action";e={if($_.EventID -eq 7001) {"Logon"} else {"Logoff"}}}
$TimeProperty = #{n="Time";e={TimeGenerated}}
Get-EventLog System -Source Microsoft-Windows-Winlogon | select $UserProperty,$TypeProperty,$TimeProperty | export-csv -path C:\Temp\TrackLogin.csv -NoTypeInformation
Your current code is very limited as it doesn't interact with the date at all. Here is a bit of code I use in a function. Should work if run as an admin.
$Days = 1
$Computer = $env:COMPUTERNAME
$events = #()
$events += Get-WinEvent -ComputerName $Computer -FilterHashtable #{
LogName='Security'
Id=#(4800,4801)
StartTime=(Get-Date).AddDays(-$Days)
}
$events += Get-WinEvent -ComputerName $Computer -FilterHashtable #{
LogName='System'
Id=#(7001,7002)
StartTime=(Get-Date).AddDays(-$Days)
}
$type_lu = #{
7001 = 'Logon'
7002 = 'Logoff'
4800 = 'Lock'
4801 = 'Unlock'
}
$ns = #{'ns'='http://schemas.microsoft.com/win/2004/08/events/event'}
$target_xpath = "//ns:Data[#Name='TargetUserName']"
$usersid_xpath = "//ns:Data[#Name='UserSid']"
If($events) {
$results = ForEach($event in $events) {
$xml = $event.ToXml()
Switch -Regex ($event.Id) {
'4...' {
$user = (Select-Xml -Content $xml -Namespace $ns -XPath $target_xpath).Node.'#text'
Break
}
'7...' {
$sid = (Select-Xml -Content $xml -Namespace $ns -XPath $usersid_xpath).Node.'#text'
$user = (New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList $sid).Translate([System.Security.Principal.NTAccount]).Value
Break
}
}
New-Object -TypeName PSObject -Property #{
Time = $event.TimeCreated
Id = $event.Id
Type = $type_lu[$event.Id]
User = $user
}
}
If($results) {
$results | sort Time -Descending
}
}
Related
I am trying to send slack message by powershell and I got stuck. here is my script. Looks like I can't use $result for -text.
What I am trying to do is to send an alert to slack channel if there is any free disk space lower than 15% or 10%.
I am using PSSlack from Powershell Gallery.
$ErrorActionPreference = "Continue";
$percentCritcal = 15;
$computers = Get-Content "D:\Disk_Usage\ServerList.txt"
$result = foreach($computer in $computers)
{
$disks = Get-WmiObject -ComputerName $computer -Class Win32_LogicalDisk -Filter "DriveType = 3"
$computer = $computer.ToUpper()
foreach($disk in $disks)
{
$Size = $disk.Size
$Freespace = $disk.FreeSpace
$sizeGB = [Math]::Round($Size / 1073741824, 2)
$freeSpaceGB = [Math]::Round($Freespace / 1073741824, 2)
$percentFree = [Math]::Round(($freespace / $size) * 100, 2);
$ResultHash = [ordered]#{
Computer = $computer
Drive = $disk.DeviceID
SizeGB = $sizeGB
UsedGB = $sizeGB - $freeSpaceGB
FreeGB = $freeSpaceGB
PercFree = $percentFree
}
New-Object -TypeName PSObject -Property $ResultHash
}
}
$Result = $Result | Where-Object {$_.PercFree -lt 10}
if ($Result)
{
$token = 'xxxxxxxxxxx'
$Result = $Result | Format-Table | Out-String
New-SlackMessageAttachment -Color $_PSSlackColorMap.red `
-Title 'Disk Free Space Alert' `
-Text $result `
-Fallback 'Bad boy' |
New-SlackMessage -Channel '#it-test' `
-IconEmoji :bomo: `
-AsUser `
-Username 'BOT' |
Send-SlackMessage -Token $token
}
The result is like this, it is repeating the same drive for couple times
Any idea?
The problem with your code is that $Result never gets populated with anything. The simplest fix would be to create an object in the ForEach loop, which will then populate $Result.
For example:
$result = foreach($computer in $computers)
{
$disks = Get-WmiObject -ComputerName $computer -Class Win32_LogicalDisk -Filter "DriveType = 3"
$computer = $computer.ToUpper()
foreach($disk in $disks)
{
$Size = $disk.Size
$Freespace = $disk.FreeSpace
$sizeGB = [Math]::Round($Size / 1073741824, 2)
$freeSpaceGB = [Math]::Round($Freespace / 1073741824, 2)
$ResultHash = [ordered]#{
Computer = $computer
deviceID = $disk.DeviceID
volName = $disk.VolumeName
percentFree = [Math]::Round(($Freespace / $Size) * 100, 2)
sizeGB = $sizeGB
freeSpaceGB = $freeSpaceGB
usedSpaceGB = $sizeGB - $freeSpaceGB
}
New-Object -TypeName PSObject -Property $ResultHash
}
}
Explanation:
The above code has taken your existing variables and turned them in to properties of a hash table #{ }. This hash table is then used to create the properties of an object with New-Object.
Because New-Object is on a line on it's own, it's result goes in to the pipeline, which is ultimately output to $Result because that's where you send the result of the ForEach.
[ordered] in front of the Hashtable should keep the properties in the order that they've been defined. Note that this requires PowerShell v3 or above.
Depending on how you want it to look, you might want to also do this to $Result after the ForEach and before you use it in the Slack Message:
$Result = '```' + ($Result | Format-Table | Out-String) + '```'
This should make the message formatted as fixed width code in a tabular format. Of course you might want to modify the output differently, just a suggestion.
I was wondering how I could return the Resolve-DnsName output from my Test-Connection script and add it to the CSV I created.
I like to capture the Name, Type, TTL, Section from that please.
Only invoke the Resolve-DnsName when the ping is not successful.
$servers = Get-Content "servers.txt"
$collection = $()
foreach ($server in $servers)
{
$status = #{ "ServerName" = $server; "TimeStamp" = (Get-Date -f s) }
$result = Test-Connection $server -Count 1 -ErrorAction SilentlyContinue
if ($result)
{
$status.Results = "Up"
$status.IP = ($result.IPV4Address).IPAddressToString
}
else
{
$status.Results = "Down"
$status.IP = "N/A"
$status.DNS = if (-not(Resolve-DnsName -Name $server -ErrorAction SilentlyContinue))
{
Write-Output -Verbose "$server -- Not Resolving"
}
else
{
"$server resolving"
}
}
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus
$collection += $serverStatus
}
$collection | Export-Csv -LiteralPath .\ServerStatus3.csv -NoTypeInformation
but nothing new is added to the CSV.
You ran into a PowerShell gotcha. PowerShell determines the columns displayed in tabular/CSV output from the first object processed. If that object doesn't have a property DNS that column won't be shown in the output, even if other objects in the list do have it. If other objects don't have properties that were present in the first object they will be displayed as empty values.
Demonstration:
PS C:\> $a = (New-Object -Type PSObject -Property #{'a'=1; 'b'=2}),
>> (New-Object -Type PSObject -Property #{'a'=3; 'b'=4; 'c'=5}),
>> (New-Object -Type PSObject -Property #{'b'=6; 'c'=7})
>>
PS C:\> $a | Format-Table -AutoSize
a b
- -
1 2
3 4
6
PS C:\> $a[1..2] | Format-Table -AutoSize
c b a
- - -
5 4 3
7 6
If you want to generate tabular output always create your objects uniformly with the same set of properties. Choosing sensible defaults even allows you to reduce your total codebase.
$collection = foreach ($server in $servers) {
$status = New-Object -Type PSObject -Property #{
'ServerName' = $server
'TimeStamp' = Get-Date -f s
'Results' = 'Down'
'IP' = 'N/A'
'HasDNS' = [bool](Resolve-DnsName -Name $server -EA SilentlyContinue)
}
$result = Test-Connection $server -Count 1 -EA SilentlyContinue
if ($result) {
$status.Results = 'Up'
$status.IP = ($result.IPV4Address).IPAddressToString
}
$status
}
I'm currently using the following code to schedule a server reboot. This works pretty well for a handful of servers but becomes a problem when there are many servers (over 80) because Register-ScheduledJob takes a long time per server.
$user = Get-Credential -UserName $env:USERNAME -Message "UserName/password for scheduled Reboot"
$trigger = New-JobTrigger -once -at $date
$script = [ScriptBlock]::Create("D:\Scripts\Scheduled-Reboot-Single.ps1 -server $server")
Register-ScheduledJob -Name $server -Credential $user -Trigger $trigger -ScriptBlock $script
My research pointed to using workflow and foreach -parallel.
The problem I run into is accurate logging. My log file is created but the columns are not ordered correctly.
workflow Do-ScheduledReboot{
Param([string[]]$servers)
foreach -parallel($server in $servers) {
InlineScript {
try {
$LastReboot = Get-EventLog -ComputerName $using:server -LogName system |
Where-Object {$_.EventID -eq '6005'} |
Select -ExpandProperty TimeGenerated |
select -first 1
#New loop with counter, exit script if server did not reboot.
$max = 20; $i = 0
do {
if ($i -gt $max) {
$hash = #{
"Server" = $using:server
"Status" = "FailedToReboot!"
"LastRebootTime" = "$LastReboot"
"CurrentRebootTime" = "FailedToReboot!"
}
$newRow = New-Object PsObject -Property $hash
$rnd = Get-Random -Minimum 5 -Maximum 40
Start-Sleep -Seconds $rnd
Export-Csv D:\workflow-results.csv -InputObject $newrow -Append -Force
exit
}#exit script and log failed to reboot.
$i++
Start-Sleep -Seconds 15
} while (Test-path "\\$using:server\c$")
$max = 20; $i = 0
do {
if ($i -gt $max) {
$hash = #{
"Server" = $using:server
"Status" = "FailedToComeOnline!"
"LastRebootTime" = "$LastReboot"
"CurrentRebootTime" = "FailedToReboot!"
}
$newRow = New-Object PsObject -Property $hash
$rnd = Get-Random -Minimum 5 -Maximum 40
Start-Sleep -Seconds $rnd
Export-Csv D:\workflow-results.csv -InputObject $newrow -Append -Force
exit
}#exit script and log failed to come online.
$i++
Start-Sleep -Seconds 15
} while (-not(Test-path "\\$using:server\c$"))
$CurrentReboot = Get-EventLog -ComputerName $using:server -LogName system | Where-Object {$_.EventID -eq '6005'} | Select -ExpandProperty TimeGenerated | select -first 1
$hash = #{
"Server" = $using:server
"Status" = "RebootSuccessful"
"LastRebootTime" = $LastReboot
"CurrentRebootTime" = "$CurrentReboot"
}
$newRow = New-Object PsObject -Property $hash
$rnd = Get-Random -Minimum 5 -Maximum 40
Start-Sleep -Seconds $rnd
Export-Csv D:\workflow-results.csv -InputObject $newrow -Append -Force
} catch {
$errMsg = $_.Exception
"Failed with $errMsg"
}#end catch
}#end inline script
}#end foreach parallel
}#end workflow
$mylist = gc D:\Servers.txt
Do-ScheduledReboot -servers $mylist
Create ordered hashtables:
$hash = [ordered]#{
'Server' = $using:server
'Status' = ...
"LastRebootTime" = ...
'CurrentRebootTime' = ...
}
I am working on a script, and I am having trouble with a section of code. When I pass in a list of computers that are all found in VCenter, the script populates the $result object correctly with a list of servers and all of the information included. If there are any errors (unable to find server in VCenter) the only thing that is returned is the error line (in the case of multiple errors, only the last error is in $result). Any ideas what I can do to resolve this?
I know that it will work if I enclose the Get-VM statement in a foreach loop, but passing one server at a time to the VCenter takes a very long time.
try {
$operation = Get-VM -Name $computers -ErrorAction Stop | Restart-VMGuest -Confirm:$false
foreach ($comp in $operation) {
$result += [pscustomobject] #{
Server = $computer
Status = $True
Error = $False
ErrorMessage = $null
Stamp = Get-Date
}
}
}
catch {
$result += [pscustomobject] #{
Server = $computer
Status = $False
Error = $True
ErrorMessage = $_
Stamp = Get-Date
}
}
I think the try / catch is on the false position. Maybe if you put an if / else statement in the foreach this work? But I don't know how to check if $comp is en error...
$operation = Get-VM -Name $computers -ErrorAction Stop | Restart-VMGuest -Confirm:$false
foreach ($comp in $operation) {
If ($comp -eq "??error??") {
$result += [pscustomobject] #{
Server = $computer
Status = $True
Error = $False
ErrorMessage = $null
Stamp = Get-Date
}
}
Else {
$result += [pscustomobject] #{
Server = $computer
Status = $False
Error = $True
ErrorMessage = $_
Stamp = Get-Date
}
}
}
I want to be able to collect information from this script and put it in the body of an email. For the most part it seems to work except I want serialnumber, osversion, model, loggedin on different lines in the body of the email.
[CmdletBinding()]
param (
$computername = (Read-Host "Enter Computer Name"),
$subject = 'Serial Numbers',
$to = 'test#test.com',
$bcc = 'test#test.com',
$body = $Collection,
#$body = (Get-Content -path "c:\powershell\serialnumber\SerialNumber.csv"),
#$mail = "mailto:$to&subject=$subject&body=$body",
[string]$ErrorLog = 'c:\powershell\useful\errorlog\retry.txt',
[switch]$LogErrors
)
[Array]$Collection = foreach($computer in $computername)
{
$os = Get-WmiObject `
Win32_OperatingSystem -computer $computer
$bios = Get-WmiObject `
Win32_BIOS -computer $computer
$model = Get-WmiObject `
Win32_ComputerSystem -computer $computer
$Monitor = Get-WmiObject -computer $computer WmiMonitorID -Namespace root\wmi |
ForEach-Object {($_.UserFriendlyName -notmatch 0 |
foreach {[char]$_}) -join ""; ($_.SerialNumberID -notmatch 0 |
foreach {[char]$_}) -join ""}
$body = New-Object -TypeName PSObject -Property #{
Computername = $computer;
LoggedIn = $Model.username;
OSVersion = $os.Caption;
SerialNumber = $bios.SerialNumber;
Model = $Model.Model;
Monitor = $Monitor;
}
#$obj | convertTo-csv | out-string
#$obj = $body
#$Collection = [string]$body
#[String]$Collection
[string]$body
Start-Process -FilePath "mailto:$to&bcc=$bcc&subject=$subject&body=$body"
}
$Collection
You can't use an object directly as the body of an email, it must be a [string]. Try this:
$body = New-Object -TypeName PSObject -Property #{
Computername = $computer;
LoggedIn = $Model.username;
OSVersion = $os.Caption;
SerialNumber = $bios.SerialNumber;
Model = $Model.Model;
Monitor = $Monitor;
} | format-list | out-string