Finding Old computers using Powershell, then sending via email - powershell-4.0

I'm new to Powershell and I'm trying to send the output via email. If I run the script in Powershell, it gives me the correct output, but the email only gives me the SearchBase OUs.
# The 60 is the number of days from today since the last logon
$xDays = (Get-Date).AddDays(-20)
# List of SearchBase locations
$OUs = #('OU1','OU2','OU3','OU4')
$OUs | ForEach-Object {Get-ADComputer -Property Name,CanonicalName,OperatingSystem,LastLogonDate -Filter {LastLogonDate -le $xDays -and Name -NotLike '*-vm'} -SearchBase $_ } | Fl Name,OperatingSystem,CanonicalName,lastLogonDate | Out-String
$From = "x#y.com"
$Rcpnt = "y#y.com"
$SMTP = "smtp.y.com"
$SUBJECT = "Old Computers Report from $env:ComputerName.$env:USERDNSDOMAIN - $((Get-Date).ToShortDateString())"
$Mail_Body = $OUs | Out-String

It is because your are exporting $OUs variable as body.
First you need to store your computers in a variable , try this
# The 60 is the number of days from today since the last logon
$xDays = (Get-Date).AddDays(-20)
# List of SearchBase locations
$OUs = #('OU1','OU2','OU3','OU4')
$OUs | ForEach-Object {$computers = Get-ADComputer -Property Name,CanonicalName,OperatingSystem,LastLogonDate -Filter {LastLogonDate -le $xDays -and Name -NotLike '*-vm'} -SearchBase $_ } | Fl Name,OperatingSystem,CanonicalName,lastLogonDate | Out-String
$From = "x#y.com"
$Rcpnt = "y#y.com"
$SMTP = "smtp.y.com"
$SUBJECT = "Old Computers Report from $env:ComputerName.$env:USERDNSDOMAIN - $((Get-Date).ToShortDateString())"
$Mail_Body = $computers | Out-String

Related

CSV comparison of Windows Services

I'm fairly new to Powershell and I'm stuck with this portion of a much larger script. I need to pull all Windows Services and compare them to see if their Startup Type of Status has changed. If there were any changes, I need to count them so I can put that value in the body of an e-mail. Also, I need to attach an HTML report showing the previous and current state of the Windows services that changed.
What I've done is the following:
That piece of code generates a CSV file showing the current state of the services.
Get-Service | Select-Object -Property Name,DisplayName,StartType,ServiceType,Status | Export-Csv -Path "C:\logs\after.csv"
Then i declare two variables, one for the current state, another one for the "template", the desired state of all Windows Services.
$before = Import-Csv -Path "C:\logs\before.csv"
$after = Import-Csv -Path "C:\logs\after.csv"
Then, i compare both of them, parsing only those service that've changed and generate a CSS styled HTML report based on that
Compare-Object $before $after -Property Name,DisplayName,StartType,ServiceType,Status | ConvertTo-html -Head $css | Set-Content "C:\logs\comparison.html"
This is what i get:
This is what it should look like:
Basically, i want to show the status of the latter CSV report in a new column after the Status column of the original CSV report. And I would also like to make a row count after that, so I can send an e-mail reporting HOW MANY services suffered any changes.
Any help will be deeply appreciated.
You could use Group-Object after Compare-Object and parse out the columns you need from that.
$before = Import-Csv -Path "C:\logs\before.csv"
$after = Import-Csv -Path "C:\logs\after.csv"
# find the differences in the StartType and Status columns. Use -PassThru to be able to process further
$groups = Compare-Object -DifferenceObject $before -ReferenceObject $after -Property StartType, Status -PassThru |
Sort-Object Name | Group-Object Name
$result = foreach ($group in $groups) {
$refGroup = $after | Where-Object { $_.Name -eq $group.Name }
# output an object with new StartType_* and Status_* columns and capture that in variable $result
$group.Group[0] |
Select-Object *, #{Name = 'StartType_Before'; Expression = {$_.StartType}},
#{Name = 'StartType_After'; Expression = {$refGroup.StartType}},
#{Name = 'Status_Before'; Expression = {$_.Status}},
#{Name = 'Status_After'; Expression = {$refGroup.Status}} -ExcludeProperty StartType,Status, SideIndicator
}
# now convert the $result to HTML and add a summary line with the number of services that have changed
$result | ConvertTo-Html -Head $css -PostContent "<br />Services affected: $($result.Count)" |
Set-Content "C:\logs\comparison.html"
If you also want output in the console do:
$result | Format-Table -AutoSize
Of course it is also possible to not use Compare-Object and do like below (will be slower, but easier to understand I guess):
$before = Import-Csv -Path "C:\logs\before.csv"
$after = Import-Csv -Path "C:\logs\after.csv"
$result = foreach ($item in $before) {
$diff = $after | Where-Object { $_.Name -eq $item.Name -and
($_.StartType -ne $item.StartType -or $_.Status -ne $item.Status) }
if ($diff) {
$item | Select-Object *, #{Name = 'StartType_Before'; Expression = {$item.StartType}},
#{Name = 'StartType_After'; Expression = {$diff.StartType}},
#{Name = 'Status_Before'; Expression = {$item.Status}},
#{Name = 'Status_After'; Expression = {$diff.Status}} -ExcludeProperty StartType,Status
}
}
# output to console
$result | Format-Table -AutoSize
# convert to HTML
$result | ConvertTo-Html -Head $css -PostContent "<br />Services affected: $($result.Count)" |
Set-Content "C:\logs\comparison.html"
Output on screen will look something like
Name DisplayName ServiceType StartType_Before StartType_After Status_Before Status_After
---- ----------- ----------- ---------------- --------------- ------------- ------------
AarSvc_8246b1 Agent Activation Runtime_8246b1 224 Manual Automatic Stopped Stopped
AdobeARMservice Adobe Acrobat Update Service Win32OwnProcess Automatic Automatic Running Stopped
ALG Application Layer Gateway Service Win32OwnProcess Manual Automatic Stopped Stopped
WdNisSvc Microsoft Defender Antivirus Network Inspection Service Win32OwnProcess Manual Manual Running Stopped

How to get disk information from computers on Active Directory with Powershell

I have basic knowledge of PowerShell and I have been given a project that needs me to create a PowerShell script that gets all the computers on the domain in active directory and gather the free space/used space of each computer.
This is what I use in order to get servers with low disk space:
Import-Module ActiveDirectory
$Servers = Get-ADcomputer -Filter {OperatingSystem -like "*Server*"} -Properties Name, OperatingSystem -SearchBase "DC=yourDN,DC=local" | Select Name
$diskReport = Foreach($Server in $Servers)
{
#$Status = "Offline"
$Name = $Server.Name
#Make sure server is online
if(Test-Connection -ComputerName $Name -ErrorAction SilentlyContinue)
{
#Get only 10%
Get-WMIObject win32_logicaldisk -ComputerName $Name -Filter "DriveType=3" -ErrorAction SilentlyContinue | Where-Object { ($_.freespace/$_.size) -le '0.1'}
}
else
{
#Server is offline
}
}
$lowservers = $diskreport | Select-Object #{Label = "Server Name";Expression = {$_.SystemName}},
#{Label = "Drive Letter";Expression = {$_.DeviceID}},
#{Label = "Total Capacity (GB)";Expression = {"{0:N1}" -f( $_.Size / 1gb)}},
#{Label = "Free Space (GB)";Expression = {"{0:N1}" -f( $_.Freespace / 1gb ) }},
#{Label = 'Free Space (%)'; Expression = {"{0:P0}" -f ($_.freespace/$_.size)}}
This will first pull all your objects using Get-ADComputer. Then it just does a simple foreach to put everything into $diskReport. The $lowservers is just to clean it up a bit.
You can do whatever you want with $lowservers. I have mine on a scheduled task to run every Monday and Friday. Then send out an email if it finds something low.

Compare mkv's creationtime

I've been tasked with creating a script that checks to see if the office cameras we've set up have stopped uploading their feeds to the "Camera" share located on our Windows 2016 storage server. If the NEWEST .mkv is over an hour old compared to the current time (get-date) then the "problem" camera needs to be restarted manually. (No need to script that part.)
Here's what my Director has written so far:
#Variable Definitions start here
$numhours = 1
Get-ChildItem "d:\Shares\Cameras" | Foreach {
$folderToLookAt = ($_.FullName + "\*.mkv")
$result = Get-ChildItem -Recurse $folderToLookAt | Sort-Object CreationTime -Descending
echo $result[0].FullName
echo $result[0].CreationTime
}
The first variable really isn't used yet, but I'm kind of dumb-struck as what to do next. The above returns the full names and creation times successfully of the newest .mkvs
Suggestions on the next part?
Invert the logic - instead of searching all the files, sorting them, finding the most recent, and checking the date, do it the other way round.
Look for files created since the cutoff, and alert if there were none found:
$cutOffTime = [datetime]::Now.AddHours(-1)
Get-ChildItem "d:\Shares\Cameras" | Foreach {
$folderToLookAt = ($_.FullName + "\*.mkv")
$result = Get-ChildItem -Recurse $folderToLookAt | Where-Object { $_.CreationTime -gt $cuttoffTime }
if (-not $result)
{
"$($_.Name) has no files since the cutoff time"
}
}
I'm assuming your paths look like:
D:\Shares\Cameras\Camera1\file1.mkv
D:\Shares\Cameras\Camera1\file2.mkv
D:\Shares\Cameras\Camera2\file1.mkv
D:\Shares\Cameras\Camera2\file2.mkv
D:\Shares\Cameras\Camera3\file1.mkv
.
.
.
If so, I would do something like this:
# The path to your files
$CameraShareRoot = 'D:\Shares\Cameras';
# Number of Hours
$NumberOfHours = 1;
# Date and time of significance. It's $NumberOfHours in the past.
$MinFileAge = (Get-Date).AddHours( - $NumberOfHours);
# Get all the folders at the camera share root
Get-ChildItem -Path $CameraShareRoot -Directory | ForEach-Object {
# Get the most recently created file in each folder
$_ | Get-ChildItem -Recurse -Filter '*.mkv' -File | Sort-Object -Property CreationTime -Descending | Select-Object -First 1
} | Where-Object {
# Remove any files that were created after our datetime
$_.CreationTime -lt $MinFileAge;
} | Select-Object -Property FullName, CreationTime
This will just output the full file name and creation time for stale cameras.
You could do something like this to email yourself a report when the results have any files:
# The path to your files
$CameraShareRoot = 'D:\Shares\Cameras';
# Number of Hours
$NumberOfHours = 1;
# Date and time of significance. It's $NumberOfHours in the past.
$MinFileAge = (Get-Date).AddHours( - $NumberOfHours);
# Get all the folders at the camera share root, save the results to $StaleCameraFiles
$StaleCameraFiles = Get-ChildItem -Path $CameraShareRoot -Directory | ForEach-Object {
# Get the most recently created file in each folder
$_ | Get-ChildItem -Recurse -Filter '*.mkv' -File | Sort-Object -Property CreationTime -Descending | Select-Object -First 1;
} | Where-Object {
# Remove any files that were created after our datetime
$_.CreationTime -lt $MinFileAge;
}
# If there are any stale camera files
if ($StaleCameraFiles) {
# Send an email
$MailMessage = #{
SmtpServer = 'mail.example.com';
To = 'youremail#example.com';
From = 'youremail#example.com';
Subject = 'Stale Camera Files';
Body = $StaleCameraFiles | Select-Object -Property FullName, CreationTime | ConvertTo-Html -Fragment | Out-String;
BodyAsHtml = $true;
}
Send-MailMessage #MailMessage;
}
Generally you will want to use LastWriteTime instead of CreationTime since the latter can be updated by a file move or copy, but maybe that's what you want here.
You have to compare the CreationTime date with (Get-Date).AddHours(-1). The AddHours method allows you to add hours to the DateTime, but also to subtract.
You can use the following example:
$Path = 'd:\Shares\Cameras'
$CreationTime = Get-ChildItem -Path $Path -Filter *.mkv |
Sort-Object -Property CreationTime -Descending |
Select-Object -First 1 -ExpandProperty CreationTime
if ($CreationTime -lt (Get-Date).AddHours(-1)) {
# your action here (restart, send mail, write output, ...)
}
It also optimizes your code a bit. ;)
$LatestFile = Get-ChildItem C:\Users\Connor\Desktop\ | Sort CreationTime | Select -Last 1
if ($LatestFile.CreationTime -gt (Get-Date).AddHours(-1)){
#It's Currently Working
} else {
#Do Other Stuff
}
try this :
Get-ChildItem "c:\temp" -Filter *.mkv -File | sort CreationTime -Descending |
select -First 1 | where CreationTime -lt (Get-Date).AddHours(-1) |
%{Write-Host "Alert !!" -ForegroundColor Red}

Get Size, location and name of each shared folder

I have this code that generates a list of all the shares and the size however can not generate a txt with this information or the shared location
$servers = #("servername")
$sizes = #()
foreach($server in $servers) {
write-host "Server: $server"
(gwmi -class Win32_Share -ComputerName $server -filter "Type = 0" |
% {
write-host " share: $($_.Name)"
$s = gci \\$server\$($_.Name) -recurse -force | Measure-Object -Property length -Sum
New-Object PSObject -property #{Name=$_.Name; Server=$server; TotalSize=$s.Sum }
})
}
And this not only shows me the size and generates txt size and can generate txt
Get-WmiObject Win32_share -computer server01 | FT "server01", path, name > ServerShares.txt
Get-WmiObject Win32_share -computer server02 | FT "server02", path, name >> ServerShares.txt
Someone could help me to create only one that does everything
In your New-Object you just need to add additional properties to get the information you want:
If you're not running PowerShell v3, remove [Ordered]
$servers = #("servername")
$sizes = #()
foreach($server in $servers)
{
write-host "Server: $server"
# Get all shares
$shares = Get-WmiObject -class Win32_Share -ComputerName $server -filter "Type = 0"
# go through each share
foreach($share in $shares)
{
write-host " share: $($share.Name)"
# Get size of share
$size = Get-ChildItem -Path "\\$server\$($_.Name)" -recurse -force | Measure-Object -Property length -Sum
# Create a new object to store information
New-Object PSObject -property ([ordered]#{
# Name of share
Name = $share.Name
# Share path
Path = $share.path
# What server share is on
Server = $server
# Total size of share
TotalSize = $size.Sum
# Change this path to where you want the file to be saved to
}) | Export-Csv -Path C:\ShareDetails.csv -NoTypeInformation -Append
}
}
I made a small revision to #Bluecakes response in order to use COM instead of .NET to capture the size information. This overcomes the path-length issues.
# Get size of share
# $size = Get-ChildItem -Path "$($share.Name)" -recurse -force | Measure-Object -Property length -Sum
$objFSO = New-Object -com Scripting.FileSystemObject
$size = "{0:N2}" -f (($objFSO.GetFolder("$($share.Name)").Size) / 1MB)
Then you also need to remove ".sum"
# Total size of share
TotalSize = $size

How do I filter directories with powershell on the amount of files contained

I am having issues finding the correct syntax I need to filter my results on only listing directories with a file count of above a specified amount (600 in my case).
This is my code so far;
$server_dir= "D:\backup"
$export_dir= "C:\support\spcount.txt"
if($server_dir)
{
$folders = Get-ChildItem $server_dir
$output = #()
foreach($folder in $folders)
{
$fname = $folder.Name
$fpath = $folder.FullName
$fcount = Get-ChildItem $fpath | Measure-Object | Select-Object -Expand Count
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount} | Format-List;
$output += $obj
}
#Output
$output | Tee-Object -FilePath $export_dir | Format-list FileCount
}
And I am getting positive results with this, it is listing all Child Items within the backup dir however I need to filter this to only display and out too text format IF the directory contains 600 or more files.
Can anybody help me please?
I am fairly new too powershell so please pull me up if this code is not the greatest, I am forever wanting too learn.
Thanks!
I think I found the issue. It's that Format-List statement at the end of your object creation statement. It pipes the newly created object through Format-List, and thus transforms it into something else.
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount} | Format-List
So if you remove that last bit, you'll get the object you expect
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount}
So when you use the where statement to filter, you'll actually have a FileCount property to filter on.
I detected it by running the $output through Get-Member which showed me it wasn't the object with the expected properties.
So basically, here's your code, including fixes:
if($server_dir)
{
# *** Added the -directory flag, cause we don't need those pesky files ***
$folders = Get-ChildItem $server_dir -directory
$output = #()
foreach($folder in $folders)
{
$fname = $folder.Name
$fpath = $folder.FullName
$fcount = Get-ChildItem $fpath | Measure-Object | Select-Object -Expand Count
# *** Format-List was dropped here to avoid losing the objects ***
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount}
$output += $obj
}
# *** And now the filter and we're done ***
$output | where -Property FileCount -ge 600 | Tee-Object -FilePath $export_dir | Format-list FileCount
}
Note also the -directory to get only folders with get-childitem, and the -ge 600 (greater than or equal) instead of -gt 599 which is just a bit more obvious.
Remember that the Format-* statements actually transform the data passed through them. So you should only use those at the end of the pipeline to show data on screen or dump it to a file.
Don't use it to transform the data you still want to work with later on.
So in short you could do something like this to get that information.
Get-ChildItem C:\temp -Directory |
Select Name,#{Label="Count";Expression={(Get-Childitem $_ -file -Recurse).Count}} |
Where-Object{$_.Count -lt 10}
Let see if we can incorporate that in your code. Your if statement is also kind of pointless. Your variable contains a non-null \ non-zerolength string so it will always be True. You want it to work if the directory exists I imagine.
$server_dir= "D:\backup"
$export_dir= "C:\support\spcount.txt"
if(Test-Path $server_dir){
Get-ChildItem C:\temp -Directory |
Select Name,#{Label="Count";Expression={(Get-Childitem $_ -file -Recurse).Count}} |
Where-Object{$_.Count -lt 10} |
ConvertTo-Csv | Tee -File $export_dir | ConvertFrom-Csv
} Else {
Write-Warning "$server_dir does not exist."
}
Just working on getting this to file and screen with Tee just a moment.
I see 2 ways to do this.
Filter it in your output like this:
$output | where -property FileCount -gt 599 | # ... your code to write to the output
Or not store it in the output array if it doesn't match the condition:
if ($fcount -gt 599) {
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount} | Format-List;
$output += obj
}

Resources