I was recently tasked with keeping track of the laptops that are encrypted with BitLocker. We have over 300 laptops and not are on the network at the same time. The person that I took over for was using the flowing command line script to create a txt for each computer.
FOR /F %%A IN (c:\Temp\BitLock\BitLock.txt) DO c:\temp\BitLock\PsExec.exe \\%%A -e cmd /c (hostname ^& Date /T ^& manage-bde.exe -status ^& manage-bde -protectors c: -get) >> \\server\Bitlocker\Recovery_Key\2015\%%A.log pause
I would like to have that information in one csv so I can quickly go through to see what is not encrypted. Opening one file at a time seems like a lot of extra work. The below code is what I have come up with in PowerShell. I am having problems getting the data to show in the cells. I don't think I am using the right object class name.
$computers= get-content c:\temp\computerlist.txt
$txtfile = "c:\temp\test\Computer4.txt"
foreach ($computer in $computers){
manage-bde -cn $compute -status |
Select "Conversion Status",Password |
export-csv c:\temp\test\Computer4.csv
}
My end goal is to have it display like below.
Computer Name Recovery Key Conversion Status Protection Status Computer Description
Name XXXXXXXXXX Fully Encrypted Protection On John, Smith Laptop
I know this is a very old post but I thought I'd still contribute an answer for anyone else that comes across this.
Here's what I came up with which involved some custom formatting as PowerShell does not see manage-bde output as a PSObject:
$computers= get-content c:\temp\computerlist.txt
$txtfile = "c:\temp\test\Computer4.txt"
$bdeObject = #()
foreach ($computer in $computers) {
$bde = manage-bde -cn $computer -status
$ComputerName = $bde | Select-String "Computer Name:"
$ComputerName = ($ComputerName -split ": ")[1]
$ConversionStatus = $bde | Select-String "Conversion Status:"
$ConversionStatus = ($ConversionStatus -split ": ")[1]
$ConversionStatus = $ConversionStatus -replace '\s','' #removes the white space in this field
$PercentageEncrypted = $bde | Select-String "Percentage Encrypted:"
$PercentageEncrypted = ($PercentageEncrypted -split ": ")[1]
#Add all fields to an array that contains custom formatted objects with desired fields
$bdeObject += New-Object psobject -Property #{'Computer Name'=$ComputerName; 'Conversion Status'=$ConversionStatus; 'Percentage Encrypted'=$PercentageEncrypted;}
}
$bdeObject | Export-Csv c:\temp\test.csv -NoTypeInformation
Also I did not see a Recovery Key or Computer Description field in manage-bde. But this gets you a csv with the Computer Name, Conversion Status, and Percentage Encrypted fields.
Related
having a heck of a time with this. Also, wanted to preface this with I'm not the best at PowerShell as I'm just starting out. I have a CSV file that I'm trying to read the first column which happens to be 'AssetName'. These are AD joined computers.
#Get Computer
$Computers = Import-csv -Delimiter ";" -Path 'C:\Path\to\File.csv' | Select-Object AssetName
$Group = "Sec Group Name"
# Set the ErrorActionPreference to SilentlyContinue, because the -ErrorAction
# option doesn't work with Get-ADComputer or Get-ADGroup.
$ErrorActionPreference = "SilentlyContinue"
# Get the computer and group from AD to make sure they are valid.
$ComputerObject = Get-ADComputer $Computer
$GroupObject = Get-ADGroup $Group
Foreach ($Computer in $Computers){
if ($GroupObject) {
# If both the computer and the group exist, remove the computer from
# the group.
Remove-ADGroupMember $Group `
-Members (Get-ADComputer $Computer).DistinguishedName -Confirm:$False
Write-Host " "
Write-Host "The computer, ""$Computer"", has been removed from the group, ""$Group""." `
-ForegroundColor Yellow
Write-Host " "
}
else {
Write-Host " "
Write-Host "I could not find the group, ""$Group"", in Active Directory." `
-ForegroundColor Red
Write-Host " "
}
}
else {
Write-Host " "
Write-Host "I could not find the computer, $Computer, in Active Directory." `
-ForegroundColor Red
Write-Host " "
}
Upon doing so, I want to remove that Asset from a specific security group. Whenever I run my script, I get this error. I don't know why it's reading it with the "#{AssetName=CompName}".
The computer, "#{AssetName=CompName}", has been removed from the group, "Sec Group Name".
Any help would be much appreciated.
With your first line you are saving a list of PSObjects to $Computers.
$Computers = Import-csv -Delimiter ";" -Path 'C:\Path\to\File.csv' | Select-Object AssetName
These Objects look like this #{AssetName=Computername}
When you iterate these objects you need to specify that you only want the value of the AssetName parameter
Get-ADComputer $Computer.AssetName
Another (in my opinion better) way would be to stop using Select-Object (which storing the returned objects in $computers) and only storing a list of AssetNames in $computers like this:
$Computers = (Import-csv -Delimiter ";" -Path 'C:\Path\to\File.csv').AssetName
EDIT:
You can also use -ExpandProperty with your Select-Object:
$Computers = Import-csv -Delimiter ";" -Path 'C:\Path\to\File.csv' | Select-Object -ExpandProperty AssetName
Firstly, I have a csv file with a list of programs that should be included with the default installation of Windows. The CSV looks like this.
Name;Version;Vendor;InstallDate
64 Bit HP CIO Components Installer;18.2.4;Hewlett-Packard;20210902
7-Zip 18.05 (x64 edition);18.05.00.0;Igor Pavlov;20210812
Adobe Acrobat Reader DC;20.006.20034;Adobe Systems Incorporated;20210903
Secondly, I have a powershell script that tries to compare the list of programs on a remote computer with the list of programs in the CSV file (by Name, Version and Vendor). It is supposed to output only the non matching programs.
The comparing part works perfectly, but now I would like to color the lines of output which match by Name and Vendor, but not by Version. How would I go about doing that?
This is my powershell script.
$programs =#()
$programs = Get-WmiObject –computername <ComputerName> -Class Win32_Product
foreach($program in $programs) {
foreach($defprogram in Import-Csv -Path "...\defprograms.csv" -Delimiter ';') {
if ($program.Name -eq $defprogram.Name -And $program.Version -eq $defprogram.Version -And $program.Vendor -eq $defprogram.Vendor) {
$programs = $programs -ne $program }
}}
$programs | sort-object Name | format-table -AutoSize Name,Version,Vendor,InstallDate
And this is the output of fore mentioned script.
In the example in the output, I would like to make the '64 Bit HP CIO Components Installer' colored red.
64 Bit HP CIO Components Installer 15.2.1 Hewlett-Packard 20210909
Canon Laser Printer/Scanner/Fax Extended Survey Program 2.1.2 CANON INC. 20210216
Not a nice solution, but do the job :
$a = #"
Name;Version;Vendor;InstallDate
64 Bit HP CIO Components Installer;18.2.4;Hewlett-Packard;20210902
7-Zip 18.05 (x64 edition);18.05.00.0;Igor Pavlov;20210812
Adobe Acrobat Reader DC;20.006.20034;Adobe Systems Incorporated;20210903
"#
$b = $a | Convertfrom-Csv -Delimiter ';'
$b | % {Write-Host "$($_.name);" -ForegroundColor Red -NoNewline; Write-Host "$($_.Vendor);" -NoNewline; Write-Host $($_.Installdate) ;}
You can do this by reading the CSV with default programs you need to colorize.
Then create a regex string of their .Name property. Use Format-Table as usual, but append Out-String -Stream so you can capture the resulting lines in a variable
# your code here:
$programs = #(Get-WmiObject –computername <ComputerName> -Class Win32_Product)
foreach($program in $programs) {
foreach($defprogram in Import-Csv -Path "...\defprograms.csv" -Delimiter ';') {
if ($program.Name -eq $defprogram.Name -And $program.Version -eq $defprogram.Version -And $program.Vendor -eq $defprogram.Vendor) {
$programs = $programs -ne $program }
}
}
# read the default programs and create a regex string of their Name fields
$defprograms = Import-Csv -Path "...\defprograms.csv" -Delimiter ';'
$redprograms = '({0})' -f (($defprograms.Name | ForEach-Object { [regex]::Escape($_) }) -join '|')
# Format-Table the array of objects and capture the resulting lines in variable `$table`
$table = $programs | Sort-Object Name | Format-Table -AutoSize Name,Version,Vendor,InstallDate | Out-String -Stream
# next loop through these lines and find lines that match any of the default program names
switch -Regex ($table) {
"^$redprograms\s*" {
Write-Host $_.Substring(0, $matches[1].Length) -NoNewline -ForegroundColor Red
Write-Host $_.Substring($matches[1].Length)
}
default { $_ }
}
Output would then look something like
I have a csv file
SERVERS|SEQUENCE|INDEX
ServerA| 1 | 1.1
ServerB| 1 | 2.1
ServerC| 2 | 3.1
And here is my Code
#importing csv into ps$
$csv = Import-Csv "sequencing.csv"
#Grouping & Sort data from csv
$objdata = $csv | Select SERVERS,SEQUENCE | Group SEQUENCE | Select #{n="SEQUENCE";e={$_.Name}},#{n="SERVERS";e={$_.Group | ForEach{$_.SERVERS}}} | Sort SEQUENCE
foreach ($d in $objdata)
{
$order = $d.SEQUENCE
$cNames = $d.SERVERS
if (Restart-Computer -ComputerName $cNames -Force -Wait -For Wmi -Delay 1 -ErrorAction SilentlyContinue)
{
write-host "$cNames is rebooting" -ForegroundColor Green
}
else
{
Write-Host "Unable to reboot $cNames remotely, Please do it manually" -ForegroundColor Red
}
}
I am trying to reboot multiple servers in sequence and piping the output through 2 different results.
From my code, all the servers will reboot but will output through the else statement.
Can anyone point me to the right direction?
Any help would be appreciated.
As Lee_Daily already pointed out, the Restart-Computer does not return anything. That means you will have to use a try/catch block.
To make sure that in the event of an error you actually enter the catch block, you need to set the ErrorAction parameter to Stop.
In your code, you show a csv to import that has the piping symbol | as delimiter, so you need to specify that on the Import-Csv cmdlet.
From your latest comment, I gather that you want to loop one computer at a time, instead of restarting multiple computers at once, so you will know which computer errors out.
Try:
# importing csv into ps$
$csv = Import-Csv "D:\sequencing.csv" -Delimiter '|'
# Grouping & Sort data from csv
$objdata = $csv | Select-Object SERVERS,SEQUENCE |
Group-Object SEQUENCE |
Select-Object #{Name = "SEQUENCE"; Expression = {$_.Name}},
#{Name = "SERVERS"; Expression = {$_.Group.SERVERS}} |
Sort-Object SEQUENCE
$objdata | ForEach-Object {
foreach ($server in $_.SERVERS) {
try {
Restart-Computer -ComputerName $server -Force -Wait -For Wmi -Delay 1 -ErrorAction Stop
Write-Host "Server $server is rebooting" -ForegroundColor Green
}
catch {
Write-Host "Unable to reboot $server remotely, Please do it manually" -ForegroundColor Red
}
}
}
Seeing your latest comment, I understand that you only want to reboot servers where the SEQUENCE value in the CSV is set to '1'. Correct?
Also, you want to restart the servers at the same time, but also want to be able to see what server did not restart. Maybe you can do that with running Restart-Computer -AsJob, but I believe that only works in PowerShell 5.1
Below code filters out all servers with SEQUENCE set to 1 and restarts them one at a time (as requested in your original question "I am trying to reboot multiple servers in sequence"):
# import csv data
$csv = Import-Csv "D:\sequencing.csv" -Delimiter '|' # change this to the delimiter actually used in the CSV !
# get the list of servers where SEQUENCE is set to '1'
$servers = $csv | Select-Object SERVERS,SEQUENCE |
Where-Object { $_.SEQUENCE.Trim() -eq '1' } |
ForEach-Object { $_.SERVERS.Trim() }
foreach ($server in $servers) {
try {
Restart-Computer -ComputerName $server -Force -Wait -For Wmi -Delay 1 -ErrorAction Stop
Write-Host "Server $server is rebooting" -ForegroundColor Green
}
catch {
Write-Host "Unable to reboot $server remotely, Please do it manually" -ForegroundColor Red
}
}
I found a script that logs all users of RDS servers which works great;
Link here
However I want to make it specific for 1 user, not all users.
I really don't know powershell so need some help.
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 = (get-date).adddays(-7)
}
$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
So, you say …
(I really don't know powershell so need some help.)
..., but point to a very advanced PowerShell script you want to use.
It is vital that you do not use anyone's code that you do not fully understand what it is doing from anyone. You could seriously damage / compromise your system(s) and or you entire enterprise. Please ramp up to protect yourself, your enterprise and avoid unnecessary confusion, complications, issues, errors and frustration you are going to encounter:
Follow this link
As for your query...
However I want to make it specific for 1 user, not all users.
… Though the script returns all users, you can just filter / prompt for the one user you are after, without changing anything about the authors code.
Prompt for a user by adding an additional parameter in that param block
[string]$targetUser = (Read-Host -Prompt 'Enter a username')
In that $FilteredOutput section, is where you'd use the additional $targetUser parameter, using the Where-Object cmdlet or string matching there or in the ….
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
… section. Something like...
($FilteredOutput -match $TargetUser) | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
I do not have an environment to test this, so, I'll leave that up to you.
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
This is all basic PowerShell 'using parameters' use case, and covered in all beginning PowerShell courses, books, websites, and built-in PowerShell help files.
I am trying to write a PowerShell script that will query all the servers in Active Directory and see the last date a Windows Update was applied.
I was having some trouble, so just to get it done, I created two scripts, one in Powershell to get the servers and the other in VBScript to query the last date. I found a this Powershell module that allows me to query the last install date, but it is extremely slow, especially on remote servers.
Here is the PS code:
Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -Properties * |
Select-Object Name | Sort-Object Name |
ForEach-Object {
Get-WUHistory -ComputerName $_.Name | Sort-Object Date,ComputerName -Descending |
Select-Object -First 1
}
Its so slow, its practically unusable.
I have some VBScript which I cobbled together that is much faster. See below:
On Error Resume Next
Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.OpenTextFile ("servers.csv", 1)
server = ""
Do Until file.AtEndOfStream
line = file.Readline
server = line
'wscript.echo server
Set objSession = CreateObject("Microsoft.Update.Session", server)
If Err.Number <> 0 Then
'WScript.Echo server & " Error: " & Err.Number & " Error (Hex): " & Hex(Err.Number) & " Source: " & Err.Source & " Description: " & Err.Description
WScript.Echo server & " Communications Error"
Err.Clear
Else
Set objSearcher = objSession.CreateUpdateSearcher
Set colHistory = objSearcher.QueryHistory(1, 1)
For Each objEntry in colHistory
Wscript.Echo server & " " & objEntry.Date
Next
End If
Loop
file.Close
Is there an easy way to get the speed of the VBScript into the Powershell code?
Here is the working Powershell code (modified again) if anyone is interested:
$ErrorActionPreference= 'silentlycontinue'
Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -Properties * | Select-Object Name |
ForEach-Object {
If (Test-Connection $_.Name -Count 1){
Get-HotFix -ComputerName $_.Name | Sort-Object InstalledOn -Descending | Select-Object -First 1
}
else {
Write-host $_.Name " Connection Error"
}
} |
Sort-Object InstalledOn
Tim Ferrill already provided you the answer but for the record you could have done something like this
$ScriptBlock = {
$hash=#{}
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$hash[$env:Computername] = $Searcher.QueryHistory(1,1) | select -ExpandProperty Date
$hash
}
Invoke-Command -ComputerName $serverlist -ScriptBlock $ScriptBlock
This would get you something like
Name Value
---- -----
Server1 5/16/2014 2:11:42 PM
Server2 4/14/2014 1:55:03 PM
Server3 5/6/2014 5:36:51 PM
Does Get-HotFix meet your needs?
Get-HotFix -ComputerName $_.Name | Measure-Object InstalledOn -Maximum
Your performance issues could be coming from Get-ADComputer. I'd do some troubleshooting to see which piece is causing you problems.
(get-hotfix -computername SERVERNAME | sort installedon)[-1] >> c:\file.txt
This will get you the last installed date. I just use some excel work to clean up the output after you run it against all your server names.
Got that command from "get-help get-hotfix -full"