PowerShell - How to count objects? - windows

I am using PowerShell to build some scripts in an Active Directory enviroment and am currently struggling to find a way to count objects. My base search is:
$DClist = (Get-ADForest).Domains | % { Get-ADDomainController –Filter * -Server $_ } | Select Site, Name, Domain
And it generates the following output:
Site Name Domain
---- ---- ------
Site-A DC-123 acme.local
Site-A DC-ABC acme.local
Site-B DC-XYZ domain.local
Site-C DC-YPT domain.local
Now I would like to count the number of objects in the column 'Name' and display something like this:
Site Count_of_Name
---- ----
Site-A 2
Site-B 1
Site-C 1
I have already tried a lot of things and the closest I got so far was using:
$DcList | Group-Object Site
But unfortunately it is not the right way to go as it only counts the number of 'Site' and "ignores" the rest. Also tried this, but it did not work as I expect either:
$DcList | Group-Object Site, Name
Please help me figure out the logic of this.
********************** UPDATE **********************
I have finally been able to come to this, but I cannot figure out a way to count the objects from 'Site' column:
$DClist | Group-Object -Property Site | ForEach-Object -Process {
[PSCustomObject]#{
Site = $_.Name
DCs = ($_.Group.Site)
}
}
Please help me out. I feel I'm so close to a solution now. :)

you are REALLY close, and the answer is about what you would expect :)
when doing group you automatically get a count property. just use this.
$DClist = (Get-ADForest).Domains | % { Get-ADDomainController –Filter * -Server $_ } | Select Site, Name, Domain
$dclist|Group-Object site|ForEach-Object{
[PSCustomObject]#{
site = $_.name
DCs = $_.group
count = $_.count
}
}
edit:
you could also do this that could be even faster if propegating through many objects. when doing select you can add a custom query and a label for that query.
#{name='fieldname';expression={$_.reference.to.object}} or #{n='field';e={$_.expression}} if you want to shorten it.
$dclist|Group-Object site|ForEach-Object{
$_|select #{n='site';e={$_.name}},count,#{n='DCs';e={$_.group}}
}

I don't exactly understand what you really want, but if it is some kind of tree, this will show it:
ForEach($Site in ($dclist | Group-Object site))
{
$Site.Count.ToString() + " " + $Site.Name
ForEach($Server in $Site.Group)
{
" + " + $Server.Name
}
}
Output:
2 Site-A
+ DC-123
+ DC-ABC
1 Site-B
+ DC-XYZ
1 Site-C
+ DC-YPT

Related

I want to search for values in a Table created with Powershell

So i have the following code to generate the entire Windows Update History:
$Session = New-Object -ComObject "Microsoft.Update.Session"
$Searcher = $Session.CreateUpdateSearcher()
$historyCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(0, $historyCount) | Select-Object Title, Description, Date,
#{name="Operation"; expression={switch($_.operation){
1 {"Installation"}; 2 {"Uninstallation"}; 3 {"Other"}
}}}
My question is how can i search in this table for updates containing KB. So for instance i want to know if the update KB4052623 has been installed. How can i write a simple piece of code to tell me KB4052326 has been found in the output of above code? Thanks in advance.
You need to assign the array returned by QueryHistory to a variable, then loop through that array to get the objects. Probably easiest to use Regex to find the KB numbers.
This code just list the found KB numbers, but you should be able test it as needed. Also, you may want to check the Description for KB numbers as well, it may tell you that it replaces old KB numbers or other important info.
$Session = New-Object -ComObject "Microsoft.Update.Session"
$Searcher = $Session.CreateUpdateSearcher()
$historyCount = $Searcher.GetTotalHistoryCount()
$MSUpdateHistory = $Searcher.QueryHistory(0, $historyCount) | Select-Object Title, Description, Date,
#{name="Operation"; expression={switch($_.operation){
1 {"Installation"}; 2 {"Uninstallation"}; 3 {"Other"}
}}}
$MSUpdateHistory | ForEach-Object {
if ($_.Title -match '(?<KBNum>KB\d+)') {
Write-Host "Found $($Matches.KBNum)"
#$_.Description
#$_.Date
#$_.Operation
}
}

How to change the loop Powershell content from the csv file automatically?

I want to change the loop PowerShell content from the CSV file automatically, is there someone know how to achieve the goal?
I want to run the loop PowerShell as below,I export the AD Group Members UPN with CSV file like One#contoso.com. I want to change the Userlist with the UPN automatically because there have more than 1000 members in the group and I have to change the upn from one#contoso.com to 'one#contoso.com',it’s easy to make a miss, is there has a smart way can achieve the goal,thanks.
Export-ADGroupmember UPN:
Get-ADGroupmember -identity adgroup | % { get-aduser $_.samaccountname | select userprincipalname } | export-csv upn.csv -notypeinformation
Loop PowerShell:
Loop PowerShell:
$UserList = #(
'One#contoso.com'
'Two#contoso.com'
'Three#contoso.com'
'Four#contoso.com'
'Five#contoso.com'
)
foreach ($UL_Item in $UserList)
{
$ARAGU_Params = #{
TenantName = "contoso"
HostPoolName = "contosoHostPool"
AppGroupName = "Desktop Application Group"
UserPrincipalName = $UL_Item
}
Add-RdsAppGroupUser #ARAGU_Params
}
You cant change content in your $UserList .More precisely, it is possible but affect the performance since You will need to read from the file and break it into segments. The best way is to download all users at once.You can do it like this:
$UserList=Get-ADGroupmember -identity adgroup | % { get-aduser $_.samaccountname | select userprincipalname }
foreach ($UL_Item in $UserList)
{
$ARAGU_Params = #{
TenantName = "contoso"
HostPoolName = "contosoHostPool"
AppGroupName = "Desktop Application Group"
UserPrincipalName = $UL_Item.userprincipalname
}
Add-RdsAppGroupUser #ARAGU_Params
}

get members of a DL

my question seems to be basic, but so far i have spent several hours already trying to figure it out. Basically the goal is to search for the transport rule that block members of a DL (Distribution List) from sending external email. then display the members of that DL too.
What the below code does is that it can display the Transport rule and the DL value in "FromMemberOf" but it doesn't display the members of it. If for example I ran just $dl_info.FromMemberOf it does show the DL groups so Im sure that is being picked up (see attached screenshots) thus I tried to use it as my array when I use the foreach-objct . ALso I noticed that its being called RawIdentity which i didnt define. ANy thoughts?
param(
[parameter(Mandatory=$true)]
[string]$sid = "SID",
[parameter(Mandatory=$true)]
[string[]]$ExchGuid = "ruleid"
)
$dl_info = #()
foreach ($rule in $ExchGuid){
$dl_info += Get-TransportRule | where {$_.guid -eq $rule} | select Priority, name, RejectMessageEnhancedStatusCode, ExceptIfFromMemberOf, FromMemberOf, #{n="DL_Block_Members";e={$dl_info.FromMemberof | ForEach-Object (Get-DistributionGroupMember $_ -ResultSize unlimited | select name)}}
}
$dl_info
I was able to resolve it by using Pscustomobject. It could display the DL member but I have to work on the script to be able to search the account membership. THats for another day
param(
[parameter(Mandatory=$true)][string]$sid = "sid",
[parameter(Mandatory=$true)][string[]]$ExchGuid = "ruleid"
)
$TRule_Info = #()
$DL_Info = #()
foreach ($rule in $ExchGuid){
$TRule_Info = Get-TransportRule | where guid -EQ $rule
foreach ($group in $TRule_Info){
$ht = [pscustomobject]#{
SID = $sid
RuleName = $TRule_Info.Name
DL_Group_Exemption = $TRule_Info.ExceptIfFromMemberOf
DL_BlockGroup_Members = $TRule_Info.FromMemberOf
}
$DL_Info += $ht
}
}
$DL_Info

How do you keep track of changes in registry using powershell?

I have a ruby script that basically checks for few keys in registry that are flags for our application. The script would check if the flag was 'on'(DWORD value 1) for longer than 14 days, it will turn it off(0) and send out an email.
To track this, what the script does is during the first run, it creates a local DB and store the flag name, date time and so on of every flag that is on in the registry. During next run, it checks this DB to find if the flag has been on for more than 14 days.
I wanted to know if there is a better way to handle this using powershell or should i create a local DB and follow the same flow?
Further info:
So i will have different accounts under '\HKEY_LOCAL_MACHINE\SOFTWARE\Company\Clients' and each client will have a debug folder which will have the flags as seen below
HKEY_LOCAL_MACHINE\SOFTWARE\Company\Clients
\Client1\Debug
IsEmail
IsShip
\Client2\Debug
IsEmail
IsPack
So i need to effectively iterate through each client, maybe create an XML/JSON or DB with the client name, flags defined and current date time. Then during a later run, i should be accessing this info, compare the registry entries and turn off flags that have been turned for longer than a days specified. I will use the timestamp saved early for this.
Update 1:
Few updates based on my updated understanding of how powershell scripts work. I will have a master array or sort with a list of flags i need. For examples $MasterFlagList=['IsEmail','IsShip','IsNew']. Now during my first run of the application, i would like to create a json string like #tukan mentioned in this answer or xml file that will be something like:
<xml>
<Client1>
<IsEmail>
<value>1</value>
<time>*current time*</time>
</IsEmail>
<IsShip>
<value>1</value>
<time>*current time*</time>
</IsShip>
<IsPack>
<value>0</value>
<time>*current time*</time>
</IsPack>
</Client1>
<Client2>
<IsEmail>
<value>0</value>
<time>*current time*</time>
</IsEmail>
<IsShip>
<value>0</value>
<time>*current time*</time>
</IsShip>
<IsPack>
<value>0</value>
<time>*current time*</time>
</IsPack>
</Client2>
</xml>
So basically what happens here is that the XML string will have all the flags from masterlist, but if there is an entry for those in registry, that value will be loaded(0 or 1) and for those not mentioned in registry, default value of 0 will be set.
Next time i run, i will load this XML from directory into an object and for each client, i need to check if the XML already has a record, if so check the date time and see if the flag has been on for an amount of time, if so turn the flag off.
A better solution to implement the idea is also welcome. I am new to powershell and that why i thought of this approach.
That is really a nasty hack. Why don't you store the date also in the registry with the client record?
I think Powershell does better job in querying registry than ruby.
To get values from a registry path:
Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug
This yields result:
IsEmail : 0
IsShip : 0
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1
PSChildName : Debug
PSProvider : Microsoft.PowerShell.Core\Registry
Alternative is to do it this way:
Get-Item -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug
Which gives:
Hive: HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1
Name Property
---- --------
Debug IsEmail : 0
IsShip : 0
If you want get the value directly, you have to do it this way:
(Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug -Name IsEmail).IsEmail
To set a value (don't forget that you probably need admin rights for that!):
Set-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug -Name 'IsEmail' -Type Dword -Value '1'
Now to the core of your question (and probably where you have problems)
I had to test it as i had different solution on my mind so it took quite a while.
# %Y ... year
# %m ... month
# %d ... day
# %R ... 24 hour time and minutes
# %S ... seconds
# example output: 20180226_10:38:23
$time_stamp= Get-Date -UFormat "%Y%m%d_%R:%S"
Write-Output $time_stamp
Get-ChildItem 'HKLM:\SOFTWARE\company\Clients' -Recurse | ForEach-Object {
$regkey = (Get-ItemProperty $_.pspath)
$regkey.PSObject.Properties | ForEach-Object {
# you could filter it via -> If($_.Name -like 'Is*'){
# $regkey is a HashTable
# Printing some output so you can check
Write-Output $regkey.PSParentPath
Write-Output $regkey.PSPath
Write-Output $_.Name ' = ' $_.Value
Write-Output ''
# convert to JSON - will contain more information than you need -> format it as you wish
$convert_registry_to_json = $regkey | ConvertTo-Json
#}
}
}
# Printing JSON output
Write-Output $convert_registry_to_json
Any further filtering or formatting I leave to the discretion.
Edit - edited question
This made me remember why I hate XML :). I have create a simple solution to your formatting. This code just a proof of concept, I recommend using functions to shorten your code (also avoiding duplicity)
$time_stamp= Get-Date -UFormat "%Y%m%d_%R:%S"
$result_hash = #{}
$result_array= #()
$first_level_hash = #{}
$second_level_hash = #{}
Get-ChildItem 'HKLM:\SOFTWARE\company\Clients' -Recurse | ForEach-Object {
$regkey = (Get-ItemProperty $_.pspath)
$regkey.PSObject.Properties | ForEach-Object {
# a HashTable
#Write-host $regkey.PSParentPath
$parent_key = $regkey.PSParentPath -Match '\w+$'
#Write-host "Found Parent:" $matches[0]
$parent_value = $matches[0]
#Write-Host $regkey.PSPath
#Write-Host $_.Name ' = ' $_.Value
#Write-host ''
$hash_key = $_.Name
$hash_value = $_.Value
If ($hash_key -like 'IsEmail'){
$second_level_hash.Add('value',$hash_value)
$second_level_hash.Add('time',$time_stamp)
$first_level_hash.Add($hash_key,$second_level_hash)
$result_hash.Add($parent_value,$first_level_hash)
$result_array += ($result_hash)
$second_level_hash=#{}
$first_level_hash=#{}
$result_hash=#{}
} ElseIf ($hash_key -like 'IsShip'){
$second_level_hash.Add('value',$hash_value)
$second_level_hash.Add('time',$time_stamp)
$first_level_hash.Add($hash_key,$second_level_hash)
$result_hash.Add($parent_value,$first_level_hash)
$result_array += ($result_hash)
$second_level_hash=#{}
$first_level_hash=#{}
$result_hash=#{}
} ElseIf ($hash_key -like 'IsPack'){
$second_level_hash.Add('value',$hash_value)
$second_level_hash.Add('time',$time_stamp)
$first_level_hash.Add($hash_key,$second_level_hash)
$result_hash.Add($parent_value,$first_level_hash)
$result_array += ($result_hash)
$second_level_hash=#{}
$first_level_hash=#{}
$result_hash=#{}
}
#$convert_registry_to_json = $regkey | ConvertTo-Json
#}
}
}
#Write-Output $result_hash
#Write-Output $result_hash.Item('IsEmail').Keys
#Write-Output $result_hash.Item('Client1').item('IsEmail')
Write-Output $result_array
ForEach ($result in $result_array) {
ForEach ($entry in $result.GetEnumerator()) {
$first_level_name = $($entry.Value)
ForEach ($second_level in $first_level_name.GetEnumerator()) {
$second_level_name = $($second_level.Value)
ForEach ($third_level in $second_level_name.GetEnumerator()) {
Write-Host "$($entry.Name) -> $($second_level.Name) -> $($third_level.Name): $($third_level.Value)"
}
}
}
}
# You can convert it to JSON
# FSpecifies how many levels of contained objects are included in the JSON representation. The default value is 2.
$convert_result_to_json = $result_array | ConvertTo-Json -Depth 3
Write-Output $convert_result_to_json
The output now is, which can be easily converted to JSON or XML if you want :
Now it yields result:
Name Value
---- -----
Client1 {IsEmail}
Client1 {IsShip}
Client2 {IsEmail}
Client2 {IsPack}
Client1 -> IsEmail -> time: 20180226_13:31:58
Client1 -> IsEmail -> value: 1
Client1 -> IsShip -> time: 20180226_13:31:58
Client1 -> IsShip -> value: 0
Client2 -> IsEmail -> time: 20180226_13:31:58
Client2 -> IsEmail -> value: 1
Client2 -> IsPack -> time: 20180226_13:31:58
Client2 -> IsPack -> value: 0
with JSON format:
[
{
"Client1": {
"IsEmail": {
"time": "20180226_14:40:02",
"value": 1
}
}
},
{
"Client1": {
"IsShip": {
"time": "20180226_14:40:02",
"value": 0
}
}
},
{
"Client2": {
"IsEmail": {
"time": "20180226_14:40:02",
"value": 1
}
}
},
{
"Client2": {
"IsPack": {
"time": "20180226_14:40:02",
"value": 0
}
}
}
]
Second edit
As for the DB. Perhaps the best solution would be to use Sqlite, check the technet wiki about it PowerShell: Accessing SQLite databases

Name of the process with highest cpu usage

I have a Samurize config that shows a CPU usage graph similar to Task manager.
How do I also display the name of the process with the current highest CPU usage percentage?
I would like this to be updated, at most, once per second. Samurize can call a command line tool and display it's output on screen, so this could also be an option.
Further clarification:
I have investigated writing my own command line c# .NET application to enumerate the array returned from System.Diagnostics.Process.GetProcesses(), but the Process instance class does not seem to include a CPU percentage property.
Can I calculate this in some way?
What you want to get its the instant CPU usage (kind of)...
Actually, the instant CPU usage for a process does not exists. Instead you have to make two measurements and calculate the average CPU usage, the formula is quite simple:
AvgCpuUsed = [TotalCPUTime(process,time2) - TotalCPUTime(process,time1)] / [time2-time1]
The lower Time2 and Time1 difference is, the more "instant" your measurement will be. Windows Task Manager calculate the CPU use with an interval of one second. I've found that is more than enough and you might even consider doing it in 5 seconds intervals cause the act of measuring itself takes up CPU cycles...
So, first, to get the average CPU time
using System.Diagnostics;
float GetAverageCPULoad(int procID, DateTme from, DateTime, to)
{
// For the current process
//Process proc = Process.GetCurrentProcess();
// Or for any other process given its id
Process proc = Process.GetProcessById(procID);
System.TimeSpan lifeInterval = (to - from);
// Get the CPU use
float CPULoad = (proc.TotalProcessorTime.TotalMilliseconds / lifeInterval.TotalMilliseconds) * 100;
// You need to take the number of present cores into account
return CPULoad / System.Environment.ProcessorCount;
}
now, for the "instant" CPU load you'll need an specialized class:
class ProcLoad
{
// Last time you checked for a process
public Dictionary<int, DateTime> lastCheckedDict = new Dictionary<int, DateTime>();
public float GetCPULoad(int procID)
{
if (lastCheckedDict.ContainsKey(procID))
{
DateTime last = lastCheckedDict[procID];
lastCheckedDict[procID] = DateTime.Now;
return GetAverageCPULoad(procID, last, lastCheckedDict[procID]);
}
else
{
lastCheckedDict.Add(procID, DateTime.Now);
return 0;
}
}
}
You should call that class from a timer (or whatever interval method you like) for each process you want to monitor, if you want all the processes just use the Process.GetProcesses static method
Building on Frederic's answer and utilizing the code at the bottom of the page here (for an example of usage see this post) to join the full set of processes gotten from Get-Process, we get the following:
$sampleInterval = 3
$process1 = Get-Process |select Name,Id, #{Name="Sample1CPU"; Expression = {$_.CPU}}
Start-Sleep -Seconds $sampleInterval
$process2 = Get-Process | select Id, #{Name="Sample2CPU"; Expression = {$_.CPU}}
$samples = Join-Object -Left $process1 -Right $process2 -LeftProperties Name,Sample1CPU -RightProperties Sample2CPU -Where {$args[0].Id -eq $args[1].Id}
$samples | select Name,#{Name="CPU Usage";Expression = {($_.Sample2CPU-$_.Sample1CPU)/$sampleInterval * 100}} | sort -Property "CPU Usage" -Descending | select -First 10 | ft -AutoSize
Which gives an example output of
Name CPU Usage
---- ---------
firefox 20.8333333333333
powershell_ise 5.72916666666667
Battle.net 1.5625
Skype 1.5625
chrome 1.5625
chrome 1.04166666666667
chrome 1.04166666666667
chrome 1.04166666666667
chrome 1.04166666666667
LCore 1.04166666666667
You might be able to use Pmon.exe for this. You can get it as part of the Windows Resource Kit tools (the link is to the Server 2003 version, which can apparently be used in XP as well).
Process.TotalProcessorTime
http://msdn.microsoft.com/en-us/library/system.diagnostics.process.totalprocessortime.aspx
Somehow
Get-Process | Sort-Object CPU -desc | Select-Object -first 3 | Format-Table CPU,ProcessName,TotalProcessorTime -hidetableheader
wasn't getting the CPU information from the remote machine. I had to come up with this.
Get-Counter '\Process(*)\% Processor Time' | Select-Object -ExpandProperty countersamples | Select-Object -Property instancename, cookedvalue| Sort-Object -Property cookedvalue -Descending| Select-Object -First 10| ft -AutoSize
Thanks for the formula, Jorge. I don't quite understand why you have to divide by the number of cores, but the numbers I get match the Task Manager. Here's my powershell code:
$procID = 4321
$time1 = Get-Date
$cpuTime1 = Get-Process -Id $procID | Select -Property CPU
Start-Sleep -s 2
$time2 = Get-Date
$cpuTime2 = Get-Process -Id $procID | Select -Property CPU
$avgCPUUtil = ($cpuTime2.CPU - $cpuTime1.CPU)/($time2-$time1).TotalSeconds *100 / [System.Environment]::ProcessorCount
You can also do it this way :-
public Process getProcessWithMaxCPUUsage()
{
const int delay = 500;
Process[] processes = Process.GetProcesses();
var counters = new List<PerformanceCounter>();
foreach (Process process in processes)
{
var counter = new PerformanceCounter("Process", "% Processor Time", process.ProcessName);
counter.NextValue();
counters.Add(counter);
}
System.Threading.Thread.Sleep(delay);
//You must wait(ms) to ensure that the current
//application process does not have MAX CPU
int mxproc = -1;
double mxcpu = double.MinValue, tmpcpu;
for (int ik = 0; ik < counters.Count; ik++)
{
tmpcpu = Math.Round(counters[ik].NextValue(), 1);
if (tmpcpu > mxcpu)
{
mxcpu = tmpcpu;
mxproc = ik;
}
}
return processes[mxproc];
}
Usage:-
static void Main()
{
Process mxp=getProcessWithMaxCPUUsage();
Console.WriteLine(mxp.ProcessName);
}
With PowerShell:
Get-Process | Sort-Object CPU -desc | Select-Object -first 3 | Format-Table CPU,ProcessName -hidetableheader
returns somewhat like:
16.8641632 System
12.548072 csrss
11.9892168 powershell

Resources