I have a Powershell script as follows:
if (something)
{
# do something
# will return 0 on success
}
else
{
exit 12345
}
I want to be able to check that there has been at least one success (return code 0) in the last 24 hours (the script will return 12345 more than 0)
In another script I have this code:
$events = #(
Get-WinEvent -FilterXml #'
<QueryList>
<Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
<Select Path="Microsoft-Windows-TaskScheduler/Operational">
*[EventData/Data[#Name='taskname']='\My Test']
</Select>
</Query>
</QueryList>
'# -ErrorAction Stop
$events | Where-Object {$_.ID -eq 102} | Select-Object *
which shows me the history of the task runs but I cannot find how I get the Run Result from here.
I can interrogate a single history item as follows:
$a = $events | Where-Object {$_.ID -eq 102} | Select-Object *
$a[0] | Get-Member
which returns
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
ActivityId NoteProperty guid ActivityId=d6ac8489-c0e1-4dbd-b06e-7ecefaf1c20c
Bookmark NoteProperty EventBookmark Bookmark=System.Diagnostics.Eventing.Reader.EventBookmark
ContainerLog NoteProperty string ContainerLog=Microsoft-Windows-TaskScheduler/Operational
Id NoteProperty int Id=102
Keywords NoteProperty long Keywords=-9223372036854775807
KeywordsDisplayNames NoteProperty ReadOnlyCollection[string] KeywordsDisplayNames=System.Collections.ObjectModel.ReadOnlyCollection`1[System.String]
Level NoteProperty byte Level=4
LevelDisplayName NoteProperty string LevelDisplayName=Information
LogName NoteProperty string LogName=Microsoft-Windows-TaskScheduler/Operational
MachineName NoteProperty string MachineName=MyPC.mydomain
MatchedQueryIds NoteProperty uint32[] MatchedQueryIds=System.UInt32[]
Message NoteProperty string Message=Task Scheduler successfully finished "{d6ac8489-c0e1-4dbd-b06e-7ecefaf1c20c}" instance of the "\My Test" task for user "MyD...
Opcode NoteProperty int16 Opcode=2
OpcodeDisplayName NoteProperty string OpcodeDisplayName=Stop
ProcessId NoteProperty int ProcessId=2544
Properties NoteProperty List[EventProperty] Properties=System.Collections.Generic.List`1[System.Diagnostics.Eventing.Reader.EventProperty]
ProviderId NoteProperty guid ProviderId=de7b24ea-73c8-4a09-985d-5bdadcfa9017
ProviderName NoteProperty string ProviderName=Microsoft-Windows-TaskScheduler
Qualifiers NoteProperty object Qualifiers=null
RecordId NoteProperty long RecordId=21093
RelatedActivityId NoteProperty object RelatedActivityId=null
Task NoteProperty int Task=102
TaskDisplayName NoteProperty string TaskDisplayName=Task completed
ThreadId NoteProperty int ThreadId=14152
TimeCreated NoteProperty datetime TimeCreated=16/11/2020 13:26:20
UserId NoteProperty SecurityIdentifier UserId=S-1-5-18
Version NoteProperty byte Version=0
But I cannot find the info I need in any of the properties. I would expect it is stored somewhere in hex format (0x3039)
Did you tried to find the output code in the XML-Output of the event?
foreach ($e in $events){
[xml]$eXmls = $e.ToXml()
$eXmls.event.EventData
}
You have what you need. You just need to expose it for viewing/capture, etc.
The example below is using the pristine, Windows Sandbox, configured logging, creating a simple task, running it once, and grabbing the results.
wevtutil set-log Microsoft-Windows-TaskScheduler/Operational /enabled:true
wevtutil get-log Microsoft-Windows-TaskScheduler/Operational
Get-WinEvent -ListLog * |
Where-Object -Property logname -match task
# Results
<#
LogMode MaximumSizeInBytes RecordCount LogName
------- ------------------ ----------- -------
Circular 10485760 37 Microsoft-Windows-TaskScheduler/Operational
Circular 1052672 8 Microsoft-Windows-TaskScheduler/Maintenance
Circular 1052672 0 Microsoft-Windows-Shell-Core/LogonTasksChannel
Circular 1052672 0 Microsoft-Windows-Mobile-Broadband-Experience-Parser-Task/Operational
Circular 1052672 0 Microsoft-Windows-BackgroundTaskInfrastructure/Operational
#>
$XmlQuery = #'
<QueryList>
<Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
<Select Path="Microsoft-Windows-TaskScheduler/Operational">
*[EventData/Data[#Name='taskname']='\TestTask']
</Select>
</Query>
</QueryList>
'#
Get-WinEvent -FilterXml $XmlQuery
# Results
<#
ProviderName: Microsoft-Windows-TaskScheduler
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
11/16/2020 2:52:16 PM 102 Information Task Scheduler successfully finished "{ca247629-6342-4e3d-9848-af234f84ae0c}" instance of the "\TestTask" task for user "F2B00BB4-0260...
11/16/2020 2:52:16 PM 201 Information Task Scheduler successfully completed task "\TestTask" , instance "{ca247629-6342-4e3d-9848-af234f84ae0c}" , action "C:\Windows\System...
11/16/2020 2:52:08 PM 110 Information Task Scheduler launched "{ca247629-6342-4e3d-9848-af234f84ae0c}" instance of task "\TestTask" for user "WDAGUtilityAccount" .
11/16/2020 2:52:08 PM 200 Information Task Scheduler launched action "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.EXE" in instance "{ca247629-6342-4e3d-9848-af234...
11/16/2020 2:52:08 PM 100 Information Task Scheduler started "{ca247629-6342-4e3d-9848-af234f84ae0c}" instance of the "\TestTask" task for user "F2B00BB4-0260-4\WDAGUtility...
11/16/2020 2:52:08 PM 129 Information Task Scheduler launch task "\TestTask" , instance "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.EXE" with process ID 5520.
11/16/2020 2:52:04 PM 106 Information User "F2B00BB4-0260-4\WDAGUtilityAccount" registered Task Scheduler task "\TestTask
#>
($events = #(
Get-WinEvent -FilterXml $XmlQuery -ErrorAction Stop
)) |
Where-Object {$PSItem.ID -eq 106} |
Select-Object -Property '*' -First 1 |
Format-List -Force
<#
Message : User "F2B00BB4-0260-4\WDAGUtilityAccount" registered Task Scheduler task "\TestTask"
Id : 106
Version : 0
Qualifiers :
Level : 4
Task : 106
Opcode : 0
Keywords : -9223372036854775808
RecordId : 1
ProviderName : Microsoft-Windows-TaskScheduler
ProviderId : de7b24ea-73c8-4a09-985d-5bdadcfa9017
LogName : Microsoft-Windows-TaskScheduler/Operational
ProcessId : 960
ThreadId : 1440
MachineName : f2b00bb4-0260-425b-b5d3-7b0331e05b80
UserId : S-1-5-18
TimeCreated : 11/16/2020 2:52:04 PM
ActivityId :
RelatedActivityId :
ContainerLog : Microsoft-Windows-TaskScheduler/Operational
MatchedQueryIds : {}
Bookmark : System.Diagnostics.Eventing.Reader.EventBookmark
LevelDisplayName : Information
OpcodeDisplayName : Info
TaskDisplayName : Task registered
KeywordsDisplayNames : {}
Properties : {System.Diagnostics.Eventing.Reader.EventProperty, System.Diagnostics.Eventing.Reader.EventProperty}
#>
(($events = #(
Get-WinEvent -FilterXml $XmlQuery -ErrorAction Stop
)) |
Where-Object {$PSItem.ID -eq 106} |
Select-Object -Property '*' -First 1).Message
# Results
<#
User "F2B00BB4-0260-4\WDAGUtilityAccount" registered Task Scheduler task "\TestTask"
#>
(($events = #(
Get-WinEvent -FilterXml $XmlQuery -ErrorAction Stop
)) |
Where-Object {$PSItem.ID -eq 106} |
Select-Object -Property '*' -First 1).Opcode
# Results
<#
0
#>
# Code Reference
<#
Op Codes Description
________ ____________
0 or 0x0 The operation completed successfully.
1 or 0x1 Incorrect function called or unknown function called.
2 or 0x2 File not found.
10 or 0xa The environment is incorrect.
0x41300 Task is ready to run at its next scheduled time.
0x41301 Task is currently running.
0x41302 Task is disabled.
0x41303 Task has not yet run.
0x41304 There are no more runs scheduled for this task.
0x41306 Task is terminated.
0x8004131F An instance of this task is already running.
0x800704DD The service is not available (is ‘Run only when a user is logged on’ checked?)
0xC000013A The application terminated as a result of a CTRL+C.
0xC06D007E Unknown software exception.
#>
Related
passing by value or reference : powershell
more information :
PSVersion 5.1.19041.906
All files can be found here : [*]https://drive.google.com/drive/folders/1Ya0Xyxewgo6FtUHVbGqSASqXOFSlXvbR?usp=sharing
I would like to try to pass an object by reference, variable. In this object I would like to pass a bunch of information (containing different other variables/data).
Sometimes there's a need to return it back (one variable), by return $menuObjts.
At paragraphs 'INFO' are $menuObjts and $menuObjts[‘MENUS’] shown.
More information about these object I have tried to figure it out by gettype().fullname.
REMARK : in the code here I've used $global: for allowing to ACCESS and CHANGE the variable and be able to make a screenshot and use it for test purpose.
So my problem is to ACCESS and CHANCE values in $menuObjts[‘MENUS’], which is a part, element of $menuObjts.
Thanks to #Santiago Squarzon for his patience and quick reaction.
The idea is to create dynamically menus from the CSV file (what works) and calls the selected functions by name - $menus_.FUNCTION which are retrieved.
But now I would like to extend it and be able to create multi sub menus.
There are two seperate MENU_GRP elements :
$menuObjts.MENU_GRP
-- contains info about the current/active/selected one
($menuObjts.**MENUS** | Where-Object {[int]**$($_).MENU_GRP** -eq ...
-- $menuObjts.MENUS : contains all posible menus (CSV)
So I import a range menu-items by a CSV file.
So these $menus_ are added to $menuObjts.MENUS / $menuObjts[‘MENUS’]
There are other features in $menus_ such as MENU, PARENT, MENU_GRP, MENU_IDX, MENU_OFFSET, MENU_SEL_TYPE, nrElems, FUNCTION, info, status , SEL, RESTART, STOP
$global:menus_ = Import-Csv -Delimiter "," $($curPath)
$menuGRP_ = 0 # 0 - MAIN
$menus_.MENU
$nrRestarts = #($menus_ | Where-Object { [int]$_.RESTART -eq 1 -and [int]$_.MENU_GRP -eq 0 }).Count
write-host (" info : - nrRestarts: {0}" -f ($nrRestarts))
# SET : values in one object : $menuObjts
$global:menuObjts =[ordered]#{
MENUS = $menus_;
MENU_GRP = $menuGRP_;
MENU_SEL_TYPE = $null;
MENU_OFFSET = $null;
nrElems = $null;
sel_input = $null;
MENU_IDX = $null}
$menuObjts.MENUS?MENU_GRP = 0 or $menuObjts.MENUS?MENU_GRP = 6
$menuObjts.MENUS?MENU_OFFSET = -1 or $menuObjts.MENUS?MENU_OFFSET = 12
$menuObjts.MENUS?nrElems = 13 or $menuObjts.MENUS?nrElems = 4
$menuObjts.MENUS ? - ? because I don't know how to retrieve the underlying object and their features/data
So my problem is how to retrieve each element of $menus_ in $menuObjts.MENUS again.
The idea is that via one variable, the next one will be calculated ([*]see function updateMenuObjtsInfo )
So my question is how can I see by type, how to get the wanted data ...
information of variables/object Get-Variable
gettype()
$menuObjts
$menuObjts.MENUS
$menuObjts.MENUS | select -first 1
These are a few things I want to achieve, but this doesn't work proper ($_).MENU_GRP
$1stElementGrp_ = $($menus_ | Where { [int]$($_).MENU_GRP -eq $menuObjts.MENU_GRP }| Select -First 1 )
$menuOFFSET_ = $($1stElementGrp_).MENU_OFFSET
$menuNrElems_ = $($1stElementGrp_).nrElems
##### where $($menuObjts.MENUS).MENU_GRP -eq $menuObjts.MENU_GRP -> .MENU_OFFSET
$menuObjts.MENU_OFFSET = $($menuObjts.MENUS | Where-Object { [int]$($_).MENU_GRP -eq $menuObjts.MENU_GRP}| Select -First 1 ).MENU_OFFSET
$menuObjts.nrElems = #($menuObjts.MENUS | Where-Object { [int]$($_).MENU_GRP -eq $menuObjts.MENU_GRP -and [int]$($_).SEL -eq 1}).Count
Another idea … was adding methods, but I’m struggling with my (little) knowledge of Powershell.
(based on 4 Ways to Create PowerShell Objects | RidiCurious.com )
$menuObjts | Add-Member -MemberType ScriptMethod -Name "getMENUS_RESTART" -Value $( this.MENUS | Where-Object { [int]$_.RESTART -eq 1 -and [int]$_.MENU_GRP -eq $menuGRP_ })
INFO - $menuObjts :
Name Value
---- -----
MENUS {#{MENU;PARENT;MENU_GRP;MENU_IDX;MENU_OFFSET;MENU_SEL_TYPE;nrElems;FUNCTION;info;status;SEL;RESTART;STOP=typeInstallation;LICENSE;0;0;-1;0;13;f1;Windows-Defende...
MENU_GRP 0
MENU_SEL_TYPE
MENU_OFFSET
nrElems
sel_input
MENU_IDX
INFO - $menuObjts.MENUS :
MENU;PARENT;MENU_GRP;MENU_IDX;MENU_OFFSET;MENU_SEL_TYPE;nrElems;FUNCTION;info;status;SEL;RESTART;STOP
-----------------------------------------------------------------------------------------------------
typeInstallation;LICENSE;0;0;-1;0;13;f1;Windows-Defender has to be uninstalled
activate;;0;1;-1;0;13;f2;Windows has to be upgraded if working with an EVALUATION prod key;-1;0;0;0
NAME;HOST;0;2;-1;0;13;f3;F-SEC has to be configured as an isolated machine on the CSI server;-1;0;0;0
IP;;0;3;-1;0;13;f4;disable default Windows NTP service;-1;0;1;0
routes;;0;4;-1;0;13;f5;disable default Windows NTP service;-1;0;0;0
users;;0;5;-1;0;13;f6;disable default Windows NTP service;-1;0;0;0
ANTI VIRUS;SERVICEs;0;6;-1;0;13;f7;disable default Windows NTP service;-1;0;0;0
NTP;;0;7;-1;0;13;f8;;-1;0;0;0
MEINBERG;;0;8;-1;0;13;f9;;-1;0;0;0
addPATH;postgres;0;9;-1;0;13;f10;;-1;0;0;0
check;after CSI;0;10;-1;0;13;f11;;-1;0;0;0
execute;;0;11;-1;0;13;f12;;-1;0;0;1
quite;;0;12;-1;0;13;f13;;-1;0;0;1
WINDOWS DEFENDER;ANTI VIRUS;6;13;12;1;4;f14;;-1;0;0;0
F-SEC;;6;14;12;1;4;f15;;-1;0;0;0
execute;;6;15;12;1;4;f16;;-1;0;0;1
quite;;6;16;12;1;4;f17;;-1;0;0;1
Additional information [2021/05/04]
PS C:\Users\Administrator> $menuObjts.MENUS | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
FUNCTION NoteProperty string FUNCTION=f1
info NoteProperty string info=Windows-Defender has to be uninstalled, before installing an other anti-virus program
MENU NoteProperty string MENU=typeInstallation
MENU_GRP NoteProperty string MENU_GRP=0
MENU_IDX NoteProperty string MENU_IDX=0
MENU_OFFSET NoteProperty string MENU_OFFSET=-1
...
PS C:\Users\Administrator> $menuObjts.MENUS
MENU : typeInstallation
PARENT : LICENSE
MENU_GRP : 0
MENU_IDX : 0
MENU_OFFSET : -1
MENU_SEL-TYPE :
nrElems : 13
FUNCTION : f1
info : Windows-Defender has ...
status : -1
SEL : 0
RESTART : 1
STOP :
MENU : activate
PARENT :
MENU_GRP : 0
...
I have the impression that $menus_ is added as a 'value' instead as an object to $menuObjts - Value : {#{MENU=
PS C:\Users\Administrator> $menuObjts.MENUS.PSobject.Properties
ReferencedMemberName : Length
ConversionType :
MemberType : AliasProperty
TypeNameOfValue : System.Int32
IsSettable : False
IsGettable : True
Value : 17
Name : Count
IsInstance : False
MemberType : Property
Value : 17
IsSettable : False
IsGettable : True
TypeNameOfValue : System.Int32
Name : Length
IsInstance : True
...
MemberType : Property
Value : {#{MENU=typeInstallation; PARENT=LICENSE; MENU_GRP=0; MENU_IDX=0; MENU_OFFSET=-1; MENU_SEL-TYPE=; nrElems=13;FUNCTION=f1; info=Windows-Defender has to be uninstalled, before installing an other anti-virus program;status=-1; SEL=0; RESTART=1; STOP=},
#{MENU=activate; PARENT=; MENU_GRP=0; MENU_IDX=1; MENU_OFFSET=-1; MENU_SEL-TYPE=; nrElems=13; FUNCTION=f2; info=Windows has to be upgraded if working with an EVALUATION prod key;status=-1; SEL=0; RESTART=0; STOP=},
#{MENU=NAME; PARENT=HOST; MENU_GRP=0; MENU_IDX=2; MENU_OFFSET=-1;MENU_SEL-TYPE=; nrElems=13; FUNCTION=f3; info=F-SEC has to be configured as an isolated machine on the CSI server;status=-1; SEL=0; RESTART=0; STOP=},
#{MENU=IP; PARENT=; MENU_GRP=0; MENU_IDX=3; MENU_OFFSET=-1; MENU_SEL-TYPE=;nrElems=13; FUNCTION=f4; info=disable default Windows NTP service; status=-1; SEL=0; RESTART=1; STOP=}...}
IsSettable : False
IsGettable : True
TypeNameOfValue : System.Object
Name : SyncRoot
IsInstance : True
...
First of all, I would recommend a good read on: Where-Object, about_Arrays and this good article on PS Objects
# Storing the CSV in the $csv var
$csv = #'
MENU;PARENT;MENU_GRP;MENU_IDX;MENU_OFFSET;MENU_SEL_TYPE;nrElems;FUNCTION;info;status;SEL;RESTART;STOP
typeInstallation;LICENSE;0;0;-1;0;13;f1;Windows-Defender has to be uninstalled
activate;;0;1;-1;0;13;f2;Windows has to be upgraded if working with an EVALUATION prod key;-1;0;0;0
NAME;HOST;0;2;-1;0;13;f3;F-SEC has to be configured as an isolated machine on the CSI server;-1;0;0;0
IP;;0;3;-1;0;13;f4;disable default Windows NTP service;-1;0;1;0
routes;;0;4;-1;0;13;f5;disable default Windows NTP service;-1;0;0;0
users;;0;5;-1;0;13;f6;disable default Windows NTP service;-1;0;0;0
ANTI VIRUS;SERVICEs;0;6;-1;0;13;f7;disable default Windows NTP service;-1;0;0;0
NTP;;0;7;-1;0;13;f8;;-1;0;0;0
MEINBERG;;0;8;-1;0;13;f9;;-1;0;0;0
addPATH;postgres;0;9;-1;0;13;f10;;-1;0;0;0
check;after CSI;0;10;-1;0;13;f11;;-1;0;0;0
execute;;0;11;-1;0;13;f12;;-1;0;0;1
quite;;0;12;-1;0;13;f13;;-1;0;0;1
WINDOWS DEFENDER;ANTI VIRUS;6;13;12;1;4;f14;;-1;0;0;0
F-SEC;;6;14;12;1;4;f15;;-1;0;0;0
execute;;6;15;12;1;4;f16;;-1;0;0;1
quite;;6;16;12;1;4;f17;;-1;0;0;1
'#|convertfrom-csv -Delimiter ';'
Get the first element of the array
$1stElementGrp_ = $csv[0] # Like this
$1stElementGrp_ = $csv | Select-Object -First 1 # Or Like this
Get the value of the property MENU_OFFSET and nrElems of the variable $1stElementGrp_
$menuOFFSET_ = $1stElementGrp_.MENU_OFFSET # $menuOFFSET_ returns -1
$menuNrElems_ = $1stElementGrp_.nrElems # $menuNrElems_ returns 13
Not sure what you're trying filter here
# $menuObjts.MENU_OFFSET = ($menuObjts.MENUS | Where-Object {
# [int]$($_).MENU_GRP -eq $menuObjts.MENU_GRP
# }| Select -First 1).MENU_OFFSET
#
# $menuObjts.nrElems = #($menuObjts.MENUS | Where-Object {
# [int]$($_).MENU_GRP -eq $menuObjts.MENU_GRP -and [int]$($_).SEL -eq 1}).Count
# }
Example: If you want to filter all the rows where MENU_OFFSET = -1
$csv | Where-Object {$_.MENU_OFFSET -eq -1} |
Select-Object MENU, PARENT, MENU_GRP, MENU_IDX, MENU_OFFSET |
Format-Table
Returns
MENU PARENT MENU_GRP MENU_IDX MENU_OFFSET
---- ------ -------- -------- -----------
typeInstallation LICENSE 0 0 -1
activate 0 1 -1
NAME HOST 0 2 -1
IP 0 3 -1
routes 0 4 -1
users 0 5 -1
ANTI VIRUS SERVICEs 0 6 -1
NTP 0 7 -1
MEINBERG 0 8 -1
addPATH postgres 0 9 -1
check after CSI 0 10 -1
execute 0 11 -1
quite 0 12 -1
Example: If you want to filter all the rows where MENU_GRP = 6 AND MENU matches the word 'WINDOWS'
$csv | Where-Object {$_.MENU_GRP -eq 6 -and $_.MENU -match 'Windows'} |
Select-Object MENU, PARENT, MENU_GRP, MENU_IDX, MENU_OFFSET |
Format-Table
Returns:
MENU PARENT MENU_GRP MENU_IDX MENU_OFFSET
---- ------ -------- -------- -----------
WINDOWS DEFENDER ANTI VIRUS 6 13 12
I wonder if you can assist me with a script (as per title)
So I've been playing around with the code below:
Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -SourceIdentifier "ProcessStarted"
The output with Get-Event returns what I need:
Get-Event | select timegenerated, #{N='ProcessName'; E = {$_.sourceeventargs.newevent.processname}}
TimeGenerated ProcessName
------------- -----------
21-Feb-20 12:58:29 PM UpdateTrustedSites.exe
21-Feb-20 12:58:31 PM backgroundTaskHost.exe
21-Feb-20 12:58:33 PM pwrgate.exe
21-Feb-20 12:58:33 PM chrome.exe
But I have no idea how to join it with win32_Process (Get-WMIObject win32_Process).CommandLine and .ExecutablePath)
Any help would be appreciated.
Register-CimIndicationEvent
The Register-CimIndicationEvent cmdlet subscribes to indications using an indication class name or a query expression. Use the SourceIdentifier parameter give a name to the subscription
And the SourceIdentifier parameter you are using is Win32_ProcessStartTrace.
Win32_ProcessStartTrace
It only has the following properties you can access
[AMENDMENT]
class Win32_ProcessStartTrace : Win32_ProcessTrace
{
uint8 SECURITY_DESCRIPTOR[];
uint64 TIME_CREATED;
uint32 ProcessID;
uint32 ParentProcessID;
uint8 Sid[];
string ProcessName;
uint32 SessionID;
};
That said, if you want to look up the Command and the Path for the process, you will have to look up the process information separately for each process.
foreach($event in Get-Event) {
$TimeGen = $event.timegenerated
$ProcessName = $event.sourceeventargs.newevent.processname
$Process = Get-WmiObject Win32_Process -Filter "Name LIKE '$ProcessName'" | select -First 1
$ProcessCMD = ($Process | select CommandLine).CommandLine
$processPath = ($Process | select ExecutablePath).ExecutablePath
$out = [pscustomobject]#{
Time=$TimeGen
Name=$ProcessName
Path=$processPath
Command=$ProcessCMD
}
$out
}
You can combine the $out paramter by use of an array or hashtable and query that further as you need.
I've created the following function for use cleaning up all references to com objects at the end of a script:
function TrashCompactor ($reflist) {
foreach ($ref in $Reflist){
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | out-null
[Runtime.InteropServices.Marshal]::FinalReleaseComObject($ref) | out-null
Remove-Variable $ref | out-null
}
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
Will Remove-variable work as I expected? Is there any harm to including [System.GC]::Collect()?
Yes, and no... as this...
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
... is a common and best practice.
Windows will always do a cleanup, but it's always clean up your environment when you are done.
As documented...
Clean Up Your PowerShell Environment by Tracking Variable Use
https://devblogs.microsoft.com/scripting/clean-up-your-powershell-environment-by-tracking-variable-use
And covered by this SO Q&A and accepted answer...
PowerShell release COM object
function Release-Ref ($ref) {
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | out-null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
because I've noted that my comobject always stay alive, I think Powershell 2.0 is not able to remove comobject no more used.
[System.Runtime.InteropServices.Marshal]::ReleaseComObject( $ref )
and that SO is exactly what you are asking, so this question is really a duplicate.
My example, I use a prefix to my variable so they are easy to find and simple globally clean up.
# Assign results to a variable and output to the screen using variable squeezing
($ponMyShell = New-Object -com "Wscript.Shell")
($ponDate = Get-Date)
($ponProcess = Get-Process |
Select -First 3)
<#
# Results
Monday, 2 March, 2020 19:40:47
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
186 14 2648 6800 0.14 15336 0 aesm_service
465 27 24300 34064 0.33 27612 22 ApplicationFrameHost
158 8 1928 4848 0.02 14268 0 AppVShNotify
SpecialFolders CurrentDirectory
-------------- ----------------
System.__ComObject C:\Windows\system32
#>
Get-Variable -Name 'pon*'
<#
# Results
Name Value
---- -----
ponDate 02-Mar-20 19:46:59
ponMyShell System.__ComObject
ponProcess {System.Diagnostics.Process (aesm_service), System.Diagnostics.Process (ApplicationFrameHost), System.Diagnostics.Process (AppVShNotify)}
#>
# Clear resource environment
Get-PSSession |
Remove-PSSession
<#
# Results
#>
[System.Runtime.InteropServices.Marshal]::
ReleaseComObject([System.__ComObject]$ponMyShell) |
Out-Null
<#
# Results
#>
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
<#
# Results
#>
Get-Variable -Name 'pon*' |
ForEach { Get-Variable -Name $_ |
Remove-Variable -Force }
# Validate clean-up
Get-Variable -Name 'pon*'
<#
# Results
#>
I am using the following Powershell command in order to extract the name, the assigned RAM and RAM usage of each VMs in the server.
Get-VM | ft Name, Memorydemand, Memoryassigned
However the result of the memorydemand and memoryassigned are in Bytes but I want them to be in Megabytes. Is there a way for me to divide the results of the memorydemand and memoryassigned by 1048576 so that I can get their corresponding MB?
Also, is it also possible to get the average RAM Usage of a certain VM for the last one or two months? Even though Hyper-V is assigning dynamic memory, I just want to double-check.
There's a couple different approaches that I can think of to achieve this.
Use Select-Object to create calculated properties
Use the Select-Object command to create custom, calculated properties.
Get-VM | Select-Object -Property `
Name,
#{ Name = 'MemoryDemandMB'; Expression = { $PSItem.MemoryDemand/1MB } },
#{ Name = 'MemoryAssignedMB'; Expression = { $PSItem.MemoryAssigned/1MB } } |
Format-Table -Property Name, MemorydemandMB, MemoryassignedMB -AutoSize
Use Add-Member to augment the objects
You can use the Add-Member command to add two new properties to the objects. This actually augments the objects, rather than simply appending the properties for the lifetime of the pipeline.
Get-VM |
Add-Member -MemberType ScriptProperty -Name MemoryDemandMB -Value { $this.MemoryDemand/1MB } -PassThru |
Add-Member -MemberType ScriptProperty -Name MemoryAssignedMB -Value { $this.MemoryAssigned/1MB } -PassThru |
Format-Table -Property Name, MemorydemandMB, MemoryassignedMB -AutoSize
Results
Here's what the output looks like on my system.
Name MemoryDemandMB MemoryAssignedMB
---- -------------- ----------------
agent01 0 0
agent02 0 0
dc01 878 1058
denver01 0 0
london01 877 1070
MobyLinuxVM 0 0
munich01 1228 1638
sccm01 2213 2604
swarm01 0 0
UbuntuDesktop 0 0
Suppose I call Get-Service and want to assign a new column ID with the cmdlet output that prints incrementing integers so that:
ID Status Name DisplayName
-- ------ ---- -----------
0 Running AdobeARMservice Adobe Acrobat Update Service
1 Stopped AeLookupSvc Application Experience
2 Stopped ALG Application Layer Gateway Service
I'm trying to use Select-Object right now to add this column, but I don't quite understand how to iterate a variable in this sort of expression. Here's what I've got:
Get-Service |
Select-Object #{ Name = "ID" ; Expression= { } }, Status, Name, DisplayName |
Format-Table -Autosize
Is there a way to iterate integers within Expression= { }, or am I going about this problem the wrong way?
You can do it this way, though you will need to maintain some counter variable outside of the main expression.
$counter = 0
Get-Service |
Select-Object #{ Name = "ID" ; Expression= {$global:counter; $global:counter++} }, Status, Name, DisplayName |
Format-Table -Autosize
Another option, which is perhaps cleaner
Get-Service `
|% {$counter = -1} {$counter++; $_ | Add-Member -Name ID -Value $counter -MemberType NoteProperty -PassThru} `
| Format-Table ID
I asked the same question a different way and got the following answer
$x = 10
Get-Service |
Select-Object #{ Name = "ID" ; Expression={ (([ref]$x).Value++) }}, Status, Name, DisplayName | Format-Table -Autosize
It wasn't at all clear to me that the expression is being invoked within Select-Object's scope, not the pipe's. The [ref] qualifier bumps the increment's result up to the pipe's scope achieving the same result as explicitly specifying the variable as global.