Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (6/18/10)
In this post:
- In Windows PowerShell, How Can I Pipe the Contents of a Property to a Command?
- Using WMI for Security Event Log Tracing
In Windows PowerShell, How Can I Pipe the Contents of a Property to a Command?
Hey, Scripting Guy! I’m working with some Windows PowerShell where the first command results in a collection of objects that have a property Name. I want to take the contents of the Name property and pipe it to a second command. If I do this, I get the desired result, but without using the pipeline:
$aggr1_volumes=get-navol -Controller $SIM01 | ? {$_.ContainingAggregate -eq "aggr1"}
get-nasnapmirror $aggr1_volumes.Name -Controller $SIM02 |
How would I, instead of using the variable and running the second command, just pipe the Name property into it? Something like the following code:
Get-navol –Controller $SIM01 | ? {$-.ContainingAggregate –eq “aggr1”} | get-nasnapmirror <what goes here to get the .name property of each object in the output from the previous command?> -Controller $SIM02
— JF
Hello JF,
To explain this, let us first start a couple of processes:
PS C:\> notepad
PS C:\> notepad
If we attempt to pipe the results from the Where-Object cmdlet directly into the Stop-Process cmdlet, an error is generated. This is shown here:
PS C:\> Get-Process | ? { $_.name -eq "notepad" } | Stop-Process $_
Stop-Process : Cannot bind argument to parameter ‘Id’ because it is null.
At line:1 char:57
+ Get-Process | ? { $_.name -eq "notepad" } | Stop-Process <<<< $_
+ CategoryInfo : InvalidData: (:) [Stop-Process], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.
Commands.StopProcessCommand
If we attempt to use the ID property from the $_ current object on the pipeline, an error is also generated. Interestingly, this is the same error that was previously generated. This is shown here:
PS C:\> Get-Process | ? { $_.name -eq "notepad" } | Stop-Process $_.id
Stop-Process : Cannot bind argument to parameter ‘Id’ because it is null.
At line:1 char:57
+ Get-Process | ? { $_.name -eq "notepad" } | Stop-Process <<<< $_.id
+ CategoryInfo : InvalidData: (:) [Stop-Process], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.
Commands.StopProcessCommand
If we look at the members of the object that is coming from the Where-Object cmdlet, we can see that it is an instance of the System.Diagnostics.Process .NET Framework class. This is shown here:
PS C:\> Get-Process | ? { $_.name -eq "notepad" } | GM
TypeName: System.Diagnostics.Process
Name MemberType Definition
—- ———- ———-
Handles AliasProperty Handles = Handlecount
Name AliasProperty Name = ProcessName
NPM AliasProperty NPM = NonpagedSystemMemorySize
PM AliasProperty PM = PagedMemorySize
VM AliasProperty VM = VirtualMemorySize
WS AliasProperty WS = WorkingSet
Disposed Event System.EventHandler Disposed(System.Object, System.Eve…
ErrorDataReceived Event System.Diagnostics.DataReceivedEventHandler ErrorDataR…
Exited Event System.EventHandler Exited(System.Object, System.Event…
OutputDataReceived Event System.Diagnostics.DataReceivedEventHandler OutputData…
BeginErrorReadLine Method System.Void BeginErrorReadLine()
BeginOutputReadLine Method System.Void BeginOutputReadLine()
CancelErrorRead Method System.Void CancelErrorRead()
CancelOutputRead Method System.Void CancelOutputRead()
Close Method System.Void Close()
CloseMainWindow Method bool CloseMainWindow()
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type reque…
Dispose Method System.Void Dispose()
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
Kill Method System.Void Kill()
Refresh Method System.Void Refresh()
Start Method bool Start()
ToString Method string ToString()
WaitForExit Method bool WaitForExit(int milliseconds), System.Void WaitFo…
WaitForInputIdle Method bool WaitForInputIdle(int milliseconds), bool WaitForI…
__NounName NoteProperty System.String __NounName=Process
BasePriority Property System.Int32 BasePriority {get;}
Container Property System.ComponentModel.IContainer Container {get;}
EnableRaisingEvents Property System.Boolean EnableRaisingEvents {get;set;}
ExitCode Property System.Int32 ExitCode {get;}
ExitTime Property System.DateTime ExitTime {get;}
Handle Property System.IntPtr Handle {get;}
HandleCount Property System.Int32 HandleCount {get;}
HasExited Property System.Boolean HasExited {get;}
Id Property System.Int32 Id {get;}
MachineName Property System.String MachineName {get;}
MainModule Property System.Diagnostics.ProcessModule MainModule {get;}
MainWindowHandle Property System.IntPtr MainWindowHandle {get;}
MainWindowTitle Property System.String MainWindowTitle {get;}
MaxWorkingSet Property System.IntPtr MaxWorkingSet {get;set;}
MinWorkingSet Property System.IntPtr MinWorkingSet {get;set;}
Modules Property System.Diagnostics.ProcessModuleCollection Modules {get;}
NonpagedSystemMemorySize Property System.Int32 NonpagedSystemMemorySize {get;}
NonpagedSystemMemorySize64 Property System.Int64 NonpagedSystemMemorySize64 {get;}
PagedMemorySize Property System.Int32 PagedMemorySize {get;}
PagedMemorySize64 Property System.Int64 PagedMemorySize64 {get;}
PagedSystemMemorySize Property System.Int32 PagedSystemMemorySize {get;}
PagedSystemMemorySize64 Property System.Int64 PagedSystemMemorySize64 {get;}
PeakPagedMemorySize Property System.Int32 PeakPagedMemorySize {get;}
PeakPagedMemorySize64 Property System.Int64 PeakPagedMemorySize64 {get;}
PeakVirtualMemorySize Property System.Int32 PeakVirtualMemorySize {get;}
PeakVirtualMemorySize64 Property System.Int64 PeakVirtualMemorySize64 {get;}
PeakWorkingSet Property System.Int32 PeakWorkingSet {get;}
PeakWorkingSet64 Property System.Int64 PeakWorkingSet64 {get;}
PriorityBoostEnabled Property System.Boolean PriorityBoostEnabled {get;set;}
PriorityClass Property System.Diagnostics.ProcessPriorityClass PriorityClass …
PrivateMemorySize Property System.Int32 PrivateMemorySize {get;}
PrivateMemorySize64 Property System.Int64 PrivateMemorySize64 {get;}
PrivilegedProcessorTime Property System.TimeSpan PrivilegedProcessorTime {get;}
ProcessName Property System.String ProcessName {get;}
ProcessorAffinity Property System.IntPtr ProcessorAffinity {get;set;}
Responding Property System.Boolean Responding {get;}
SessionId Property System.Int32 SessionId {get;}
Site Property System.ComponentModel.ISite Site {get;set;}
StandardError Property System.IO.StreamReader StandardError {get;}
StandardInput Property System.IO.StreamWriter StandardInput {get;}
StandardOutput Property System.IO.StreamReader StandardOutput {get;}
StartInfo Property System.Diagnostics.ProcessStartInfo StartInfo {get;set;}
StartTime Property System.DateTime StartTime {get;}
SynchronizingObject Property System.ComponentModel.ISynchronizeInvoke Synchronizing…
Threads Property System.Diagnostics.ProcessThreadCollection Threads {get;}
TotalProcessorTime Property System.TimeSpan TotalProcessorTime {get;}
UserProcessorTime Property System.TimeSpan UserProcessorTime {get;}
VirtualMemorySize Property System.Int32 VirtualMemorySize {get;}
VirtualMemorySize64 Property System.Int64 VirtualMemorySize64 {get;}
WorkingSet Property System.Int32 WorkingSet {get;}
WorkingSet64 Property System.Int64 WorkingSet64 {get;}
PSConfiguration PropertySet PSConfiguration {Name, Id, PriorityClass, FileVersion}
PSResources PropertySet PSResources {Name, Id, Handlecount, WorkingSet, NonPag…
Company ScriptProperty System.Object Company {get=$this.Mainmodule.FileVersio…
CPU   ScriptProperty System.Object CPU {get=$this.TotalProcessorTime.TotalS…
Description ScriptProperty System.Object Description {get=$this.Mainmodule.FileVe…
FileVersion ScriptProperty System.Object FileVersion {get=$this.Mainmodule.FileVe…
Path ScriptProperty System.Object Path {get=$this.Mainmodule.FileName;}
Product ScriptProperty System.Object Product {get=$this.Mainmodule.FileVersio…
ProductVersion ScriptProperty System.Object ProductVersion {get=$this.Mainmodule.Fil…
But we already know that just attempting to access the members directly fails. This is because of the way the Where-Object cmdlet interrupts the pipeline. To work with a property directly on the other side of the pipeline from the Where-Object, it is necessary to use the Foreach-Object cmdlet. This is shown here:
PS C:\> Get-Process | ? { $_.name -eq "notepad" } | % { Stop-Process $_.id }
PS C:\>
Using WMI for Security Event Log Tracing
Hey, Scripting Guy! I am working on a script to do security event log tracing with tight date/time range queries for good performance. I have used WMI for this, but the problem is that I wrote this script for a team that does not have Domain Admins rights, but needs to use WMI to read domain controller logs. I know I can adjust WMI security settings to fix this, but is there an easy way to do this for a few hundred domain controllers by scripting it? I would hate to do this one at a time, and other methods of reading the logs are proving far too slow. The code below is what I am talking about:
(get-eventlog and system.diagnostics.eventlog)
— NL
Hello NL,
You know that to grant people access to remote WMI information, they must be a member of the local administrators group by default. This article on MSDN talks about remote WMI security. If you do not wish to do this, you will need to add them to a group and explicitly grant them access to a WMI namespace. On Windows Vista and later, this can be done by using the SetSecurityDescriptor method from the __SystemSecurity class. An instance of this class exists for each namespace in WMI. Namespace security is shown in the following image.
You cannot, however, grant them permission to only the Eventlog classes. They will have access to all the WMI classes in the namespace. You could create your own WMI namespace, but that is a different story.
The __SystemSecurity class is documented on MSDN. You can find information about the SetSecurityDescriptor method there as well. The SetSecurityDescriptor method requires an instance of a Win32_SecurityDescriptor class for its argument. This class is in turn composed of two other classes: Win32_ACE and Win32_Trustee. You would have to use Win32_ACE to provide the DACLS and the SACLS you wish to grant to your group. Use Win32_Trustee to specify the group name and the owner of the group. After you have created the Win32_SecurityDescriptor, it is a pretty simple matter to call the SetSecurityDescriptor method.
Before the SetSecurityDescriptor method became available in Windows Vista, one trick I used to use was to use the SetSD method. This method has been available since Windows 2000, and is described in my Microsoft Press book, Windows Scripting with WMI: Self-Paced Learning Guide. What you do is configure the WMI namespace permissions you wish to create by using the WMI Control Tool on a test machine. You then use the GetSD method from the __SystemSecurity class to copy the byte array that represents the security for the namespace. Save this value, and then use the SetSD method to apply the security to the other machines.
As a matter of fact, because of the difficulty inherit in properly managing the three different WMI classes required to manually create an instance of the Win32_SecurityDescriptor WMI class, you may wish to use a similar approach to creating your Security Descriptor. Manually set the namespace permissions on a test machine, use the GetSecurityDescriptor WMI method from the __SystemSecurity class, and then set it using the SetSecurityDescriptor method.
All of this is only half of the solution. The other task is to set the DCOM permissions to allow the group remote access to WMI. In Windows Vista and later, there is the Win32_DCOMApplicationSetting WMI class that has several methods that will allow you to remotely configure the DCOM security settings. The methods you would be most interested in are SetLaunchSecurityDescriptor and SetAccessSecurityDescriptor.
Keep in mind, that when you write your script, you will need to specify the SeSecurityPrivilege and the SeRestorePrivilege in VBScript. In Windows PowerShell 2.0, you simply add the -EnableAllPrivileges switch.
On a Windows Server 2003 or earlier operating system, the Win32_DCOMApplicationSetting WMI class does not exist. There is sample code in the Windows Server 2008 Software Development Kit (SDK) called DCOMPERM that you could compile into an executable and use to make the DCOM settings.
Well, this concludes another edition of Quick-Hits Friday. Join us tomorrow for the Weekend Scripter as we delve into the mysteries of…well, we will let that remain a mystery for now.
If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson and Craig Liebendorfer, Scripting Guys
0 comments