Very new to PowerShell and WMI. Watched Don Jones, “My Final 1-Day PowerShell v2 Workshop” and have a copy of the “Windows PowerShell Cookbook, Third Edition.”
I’m trying to write a PowerShell script that helps a user with copy and compare of a CD-ROM. Is there data in the top drive? Is there a blank CD-ROM in the lower drive? etc.
My plan is to
subscribe to WMI events about the CD-ROM drives
display a form prompting the user to do the right thing
only enable the OK button when both drives meet the above conditions
Unfortunately, my event does not appear to be triggered by the opening or closing of the CD-ROM drive.
Am I listening for the wrong event? Am I not subscribing to events properly?
Any help would be appreciated.
##############################################################################
##
## Copy--CD.ps1
##
## From Select-GraphicalFilteredObject.ps1 in Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
Copy-CD
#>
[CmdletBinding()]
Param()
Set-StrictMode -Version 2
Write-Verbose "Load the Windows Forms assembly"
Add-Type -Assembly System.Windows.Forms
Write-Verbose "Create the main form"
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size @(300,300)
Write-Verbose "Create a label for status"
$label = New-Object Windows.Forms.Label
$label.Anchor = "Left"
$label.Text = "My status"
$label.Dock = "Top"
Write-Verbose "Create the button panel to hold the OK and Cancel buttons"
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size @(300,30)
$buttonPanel.Dock = "Bottom"
Write-Verbose "Create the Cancel button, which will anchor to the bottom right"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 5
$cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Anchor = "Right"
Write-Verbose "Create the OK button, which will anchor to the left of Cancel"
$okButton = New-Object Windows.Forms.Button
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Enabled = $false
$okButton.Top = $cancelButton.Top
$okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Anchor = "Right"
Write-Verbose "Add the buttons to the button panel"
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
function Get-CDDrives {
@(Get-WMIobject win32_logicaldisk -filter 'DriveType=5' | Select Caption, Drive, MediaType, Access )
}
$drives = @(Get-CDDrives)
Write-Verbose $drives[0]
if ($drives[0].Access) {
Write-Verbose $drives[0].Access
}
else {
Write-Verbose "Drive is empty"
}
$dataCD = $drives[0].Access -eq 1
Write-Verbose $dataCD
$okButton.Enabled = $dataCD
if ($dataCD) {
$label.Text = "Data CD ready."
}
else {
$label.Text = "Please place the Data CD to be copied in the top tray."
}
Write-Verbose "Add the button panel and list box to the form, and also set"
Write-Verbose "the actions for the buttons"
$form.Controls.Add($label)
$form.Controls.Add($buttonPanel)
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.Add_Shown( { $form.Activate() } )
Write-Verbose "Listen for events."
$WMI = @{
Query = "select * from __InstanceModificationEvent within 3 where TargetInstance ISA 'Win32_LogicalDisk'"
Action = { $okButton.Enabled = $false }
}
Write-Verbose "Unregister any Event subscriber still hanging around"
Get-EventSubscriber | Unregister-Event
Register-WMIEvent @WMI
Write-Verbose "Show the form, and wait for the response"
$result = $form.ShowDialog()
function Copy-CD {
Write-Verbose "Copying the CD..."
}
if($result -eq "OK")
{
Copy-CD
}
else {
Write-Output "Never mind"
}
That’s because you’re listening for modifications on Win32_LogicalDisk, and the CD drive doesn’t “change” when it becomes empty or non-empty. It’s still a logical disk either way. PowerShell gets a bit tricky with event-driven stuff, because that wasn’t PowerShell’s first design goal.
https://support.microsoft.com/en-us/kb/163503 kind of covers the event Windows generates for insertion/deletion, but trapping that in PowerShell is probably going to be a lot harder than you thought. Get-Event might be a starting point, but I’m not sure how .NET-friendly that system-level event is going to be.
PowerShell’s console host likely gets the event, but I doubt it’s passing it into the .NET stack. That was kinda my concern. PowerShell wasn’t really born for system-level event monitoring, per se. It’s WMI stuff is okay, but I’m not sure the WMI stack itself is a responder for that particular event. All that stuff is really designed for native code.
The user will be the one opening and closing the drives.
PowerShell wants to detect when the drive opens or closes. Then PowerShell will go back and check to see if the user finally got a data CD in the top drive and a blank one in the bottom drive. At that point PowerShell will enable the OK button.
If events won’t work, PowerShell could occasionally poll until the user gets it right or cancels.
Alas, even my polling approach doesn’t work. Using some deep magic from the Windows PowerShell Cookbook, a simple timer should check the CD Drive every second.
The timer doesn’t appear to be running. No output from the timer is reported when ShowDialog is replaced with an infinite loop.
Again, your help would be much appreciated.
##############################################################################
##
## Copy-CD.ps1
##
## From Select-GraphicalFilteredObject.ps1 in Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
Copy-CD
#>
[CmdletBinding()]
Param()
Set-StrictMode -Version 2
Write-Verbose "Load the Windows Forms assembly"
Add-Type -Assembly System.Windows.Forms
Write-Verbose "Create the main form"
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size @(300,300)
Write-Verbose "Create a label for status"
$label = New-Object Windows.Forms.Label
$label.Anchor = "Left"
$label.Text = "My status"
$label.Dock = "Top"
Write-Verbose "Create the button panel to hold the OK and Cancel buttons"
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size @(300,30)
$buttonPanel.Dock = "Bottom"
Write-Verbose "Create the Cancel button, which will anchor to the bottom right"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 5
$cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Anchor = "Right"
Write-Verbose "Create the OK button, which will anchor to the left of Cancel"
$okButton = New-Object Windows.Forms.Button
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Enabled = $false
$okButton.Top = $cancelButton.Top
$okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Anchor = "Right"
Write-Verbose "Add the buttons to the button panel"
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
function Get-CDDrives {
@(Get-WMIobject win32_logicaldisk -filter 'DriveType=5' | Select Caption, Drive, MediaType, Access )
}
$drives = @(Get-CDDrives)
Write-Verbose $drives[0]
if ($drives[0].Access) {
Write-Verbose $drives[0].Access
}
else {
Write-Verbose "Drive is empty"
}
function Check-Drive-Status {
$dataCD = $drives[0].Access -eq 1
Write-Verbose $dataCD
$okButton.Enabled = $dataCD
if ($dataCD) {
$label.Text = "Data CD ready."
}
else {
$label.Text = "Please place the Data CD to be copied in the top tray."
}
}
Check-Drive-Status
Write-Verbose "Add the button panel and list box to the form, and also set"
Write-Verbose "the actions for the buttons"
$form.Controls.Add($label)
$form.Controls.Add($buttonPanel)
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.Add_Shown( { $form.Activate() } )
Write-Verbose "Unregister any Event subscriber still hanging around"
Get-EventSubscriber | Unregister-Event
Write-Verbose "Start a timer to check the CD."
$timer = New-Object Timers.Timer
$timer.Interval = 1000
$timer.AutoReset = $true
Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed -Action { Write-Verbose "Pretending to check the CD" }
$timer.Enabled = $true
$timer.start()
Get-EventSubscriber
Write-Verbose "Show the form, and wait for the response"
while($true) { Write-Verbose "Processing loop"; Sleep 1 }
$result = $form.ShowDialog()
function Copy-CD {
Write-Verbose "Copying the CD..."
}
if($result -eq "OK")
{
Copy-CD
}
else {
Write-Output "Never mind"
}
Thanks to all for your help! Curtis’s approach to check CD drive status is an improvement over my original implementation.
The problem remained, how to check CD status while the form was displayed? This example provides the needed clue. Don’t use Timers.Timer, use System.Windows.Forms.Timer!
While this is working, there is still magic I don’t understand. What is the difference between “function CheckDriveStatus {…}” and “$CheckDriveStatus = {…}”? timer.addTick() only accepts $CheckDriveStatus not the function. Calling $CheckDriveStatus only echos the text of the code, but apparently doesn’t run the code.
##############################################################################
##
## Copy-CD.ps1
##
## From Select-GraphicalFilteredObject.ps1 in Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
Copy-CD
#>
[CmdletBinding()]
Param()
Set-StrictMode -Version 2
Write-Verbose "Load the Windows Forms assembly"
Add-Type -Assembly System.Windows.Forms
Write-Verbose "Create the main form"
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size @(300,300)
Write-Verbose "Create a label for status"
$label = New-Object Windows.Forms.Label
$label.Anchor = "Left"
$label.Text = "My status"
$label.Dock = "Top"
Write-Verbose "Create the button panel to hold the OK and Cancel buttons"
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size @(300,30)
$buttonPanel.Dock = "Bottom"
Write-Verbose "Create the Cancel button, which will anchor to the bottom right"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 5
$cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Anchor = "Right"
Write-Verbose "Create the OK button, which will anchor to the left of Cancel"
$okButton = New-Object Windows.Forms.Button
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Enabled = $false
$okButton.Top = $cancelButton.Top
$okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Anchor = "Right"
Write-Verbose "Add the buttons to the button panel"
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
$CheckDriveStatus = {
$drives = @(Get-WMIObject win32_cdromdrive)
$mediaLoaded = $drives[0].MediaLoaded
$dataCD = $mediaLoaded -and $drives[0].size
Write-Verbose $dataCD
$okButton.Enabled = $dataCD
if ($dataCD) {
$label.Text = "The Data CD is ready."
}
elseif ($mediaLoaded) {
$label.Text = "The Data CD in the top drive is blank."
}
else {
$label.Text = "Please place the Data CD to be copied in the top tray."
}
}
Write-Verbose "Add the button panel and list box to the form, and also set"
Write-Verbose "the actions for the buttons"
$form.Controls.Add($label)
$form.Controls.Add($buttonPanel)
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.Add_Shown( { $form.Activate() } )
Write-Verbose "Start a timer to check the CD."
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Enabled = $true
$timer.add_tick($CheckDriveStatus)
$timer.start()
Write-Verbose "Show the form, and wait for the response"
$result = $form.ShowDialog()
function Copy-CD {
Write-Verbose "Copying the CD..."
}
if($result -eq "OK")
{
Copy-CD
}
else {
Write-Output "Never mind"
}