WMI: How to determine InstanceName of active instance? - vba

I'm still a bit shaky when dealing with WMI, but here's the scenario. My company has several models of HP laptops deployed across the domain, and we need to change the asset tag setting in the BIOS. This code works for all of our HP EliteBooks:
Set objWMIService = objSWbemLocator.ConnectServer( _
strComputer, "root\HP\InstrumentedBIOS")
'We have to search for the exact bios tag name because it varies by model
Set colItems = objWMIService.ExecQuery("SELECT * FROM HPBIOS_BIOSString", , 48)
For Each objItem In colItems
If InStr(1, objItem.Name, "Asset") Then strName = objItem.Name
Next
' Obtain an instance of the the class using a key property value.
Set objShare = objWMIService.Get( _
"HPBIOS_BIOSSettingInterface.InstanceName='ACPI\PNP0C14\0_0'")
' Obtain an InParameters object specific to the method.
Set objInParam = objShare.Methods("SetBIOSSetting").InParameters.SpawnInstance_()
' Add the input parameters.
objInParam.Properties_.item("Name") = strName
objInParam.Properties_.item("Value") = strAssetTag
objInParam.Properties_.item("Password") = "<utf-16/>" & strPassword
' Execute the method and obtain the return status.
' The OutParameters object in objOutParams is created by the provider.
Set objOutParams = objWMIService.ExecMethod( _
"HPBIOS_BIOSSettingInterface.InstanceName='ACPI\PNP0C14\0_0'", _
"SetBIOSSetting", objInParam)
Select Case objOutParams.return
Case 0
strReturn = "Success. Asset Tag " & strAssetTag & _
" will be configured the next time you reboot " & _
strComputer & "."
Case 1
strReturn = "1: Not Supported"
Case 2
strReturn = "2: Unspecified Error"
Case 3
strReturn = "3: Timeout"
Case 4
strReturn = "4: Failed"
Case 5
strReturn = "5: Invalid Parameter"
Case 6
strReturn = "6: Access Denied"
Case Else
strReturn = "..."
End Select
The problem is that on HP ZBooks, the InstaneName is ACPI\PNP0C14\1_0. So changing the line to the correct instance makes it work for the ZBooks but breaks it for the EliteBooks. Eg:
Set objShare = objWMIService.Get( _
"HPBIOS_BIOSSettingInterface.InstanceName='ACPI\PNP0C14\1_0'")
I could create two scripts, one for the EliteBooks and one for the ZBooks, but since there is always only one Active instance, I would rather create one script that gets that active instance. For example (non-working code that I wished worked):
Set objShare = objWMIService.Get( _
"HPBIOS_BIOSSettingInterface.Active='True'")
So, how can I find the InstanceName of the Active HPBIOS_BIOSSettingInterface?

So maybe this was too simple or maybe there's a better way to do this, but here's how I ended up solving it:
I simply reused the colItems object to get a collection of Instances. There should only be one, but I verify that I have the correct instance by checking it's .Active property.
'We have to search for the exact bios tag name because it varies by model
Set colItems = objWMIService.ExecQuery( _
"SELECT * FROM HPBIOS_BIOSString", , 48)
For Each objItem In colItems
If InStr(1, objItem.Name, "Asset") Then strTagName = objItem.Name
Next
'We have to search for the exact instance name because it varies by model
Set colItems = objWMIService.ExecQuery( _
"SELECT * FROM HPBIOS_BIOSSettingInterface", , 48)
For Each objItem In colItems
If objItem.Active = "True" Then
strInstanceName = objItem.InstanceName
End If
Next
' Obtain the active instance.
Set objShare = objWMIService.Get( _
"HPBIOS_BIOSSettingInterface.InstanceName='" & strInstanceName & "'")
Not sure if this is the cleanest method for doing this, but it works.

Related

Programatically add or remove a user for system wide 'Access Permissions' using vb.net

I have successfully managed to come up with a procedure for changing APPLICATION SPECIFIC 'Access Permissions' using vb.net. This is the equivalent of running 'dcomcnfg' and changing the setting by selecting the 'Component services\Computers\My Computer\DCOM Config' folder and the specific application. By right clicking on the application and selecting properties and the security tab, different user accounts can be added or removed. This works fine with this code shown below.
I'm struggling though to come up with code that will change the SYSTEM WIDE equivalent 'Access Permissions'. The code should be the equivalent or running 'dcomcnfg' and changing the the setting by right clicking on My Computer in 'Component services\Computers\My Computer' and selecting properties and the security tab.
I'm hoping that I can modify my existing code but because I am trying to change a system wide setting rather than an application specific setting i'm hitting a roadblock. I've done a lot of searching on google but cannot work it out. Any advice is appreciated.
Private Sub ChangeApplicationDcomAccessSecuritySettings(AddUser As Boolean, RemoveUser As Boolean)
Dim strComputer As String = "."
Dim objWMIService As New Object
objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate, (Security)}!\\" & strComputer & "\root\cimv2")
' Get an instance of Win32_SecurityDescriptorHelper
Dim objHelper As New Object
objHelper = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2:Win32_SecurityDescriptorHelper")
' Obtain an instance of the the class
' using a key property value.
Dim objCosmosApp As Object = objWMIService.Get("Win32_DCOMApplicationSetting.AppID='" & OPCServerApplicationID.Trim & "'")
' Get the existing security descriptor for the App
Dim objSD As New Object
objSD = Nothing
Dim ret As Object
ret = objCosmosApp.GetAccessSecurityDescriptor(objSD)
If ret <> 0 Then
MessageBox.Show("Could not get security descriptor: " & ret)
End If
' Convert file security descriptor from Win32_SecurityDescriptor format to SDDL format
Dim SDDLstring As String = ""
ret = objHelper.Win32SDToSDDL(objSD, SDDLstring)
If ret <> 0 Then
MessageBox.Show("Could not convert to SDDL: " & ret)
Else
End If
' Set the Launch security descriptor for the App
' the sidString here the is the securityidentifier for the username that is to be added or removed converted to a string
If AddUser = True And RemoveUser = False Then
SDDLstring = SDDLstring & "(A;;CCDCLCSWRP;;;" & sidString & ")"
End If
If AddUser = False And RemoveUser = True Then
Dim temporarystring As String = "(A;;CCDCLCSWRP;;;" + sidString + ")"
SDDLstring = SDDLstring.Replace(temporarystring, "")
End If
ret = objHelper.SDDLToWin32SD(SDDLstring, objSD)
If ret <> 0 Then
MessageBox.Show("Could not translate SDDL String to Win32SD: " & ret)
End If
ret = objCosmosApp.SetaccessSecurityDescriptor(objSD)
If ret <> 0 Then
MessageBox.Show("Could not set security descriptor: " & ret)
End If
End Sub

Weird characters in email body

I have a little problem with VBScript. There is how it should work. It is a simply code that should go through all emails in particular folder, get particular email body and try to find regular expression. It works correctly on my computer but somehow the same code is not working on other laptop (my friend laptop). Most (not all of them) of emails body look very weird like on attached screen below:
I would like to add that we had the same email messages to test. What is also curious, after use script, it converts first email into these weird characters.
And this is how code looks:
Set objOutlook = CreateObject("Outlook.Application")
Set objNamespace = objOutlook.GetNamespace("MAPI")
Set objMailbox = objNamespace.Folders(Main_mailbox)
Set objMainMailbox = objMailbox.Folders(Main_folder)
Set objFolder = objMainMailbox.Folders(Sub_folder)
Set re = New RegExp
re.IgnoreCase = True
re.Global = True
re.Pattern = "<.+>"
Set colItems = objFolder.Items
NumberOfEmails = colItems.Count
WScript.Echo NumberOfEmails & " emails found"
For i = NumberOfEmails To 1 Step - 1
BodyMsg = colItems(i).Body
Lines = Split(BodyMsg, vbCrlf)
For j = 1 To UBound(Lines)
If InStr(1, Lines(j), "Reply-To:") Then
Set RegMatches = re.Execute(Lines(j))
For Each myMatch In RegMatches
OutputMatch = OutputMatch & " " & myMatch & ";"
OutputMatch = Replace(OutputMatch, "<", "", 1, 1)
OutputMatch = Replace(OutputMatch, ">", "", 1, 1)
EmailCount = EmailCount + 1
Next
End If
Next
Next
I am wondering if it is about encoding or something like that and if that problem is caused by system settings?
If you need some more information that I forgot mention about, please let me know.
That sure sounds like you are running into an NDR (Non-Delivery Report - represented by the ReportItem object) - ReportItem.Body returns gibberish when accessed though the Outlook Object Model. This has been a problem for a few versions of Outlook now.
You can either skip items like this by checking that you only get the MailItem object (Class property must be 43 (olMail)) or use Redemption (I am its author) - its RDOReportItem object does not have this problem.

How to uniquely identify an Outlook email as MailItem.EntryID changes when email is moved

My company uses a single email address for customers to send requests and orders to. we created an Access database that import emails into a table. The table creates it's own unique identifier for each email imported but is not supposed to import an email twice. The system was working as we were only concerned with emails coming into the inbox and didn't need anything more than that.
However we now need to know the "flow", "traffic" and "workload" of the email pool that this account is. The email that comes into the inbox is categorized and then moved to a folder called "my_tasks" and a subfolder the folder named as 1 of the four CSRs to be worked on by a manager. This email is then dealt with and the CSR moves it to a subfolder under another folder called "Completed".
So email comes into Inbox, gets moved to my_tasks\joeblow is dealt with and gets moved to Completed\Canada.
Currently I have code that iterates through the folders and finds each email, grabs the fields we want to store and then inserts them into the table. All of this is done in Access through VBA code.
Private Sub ImportEmailItem(objMailItem As Outlook.MailItem)
On Error GoTo ImportEmailItem_Error
' Set up DAO objects
Dim rstMB As DAO.Recordset
Dim dskippedFolderMailCount As Double
Dim strSQLrMB As String
strSQLrMB = "SELECT * FROM tblMailBox WHERE OLID='" & objMailItem.EntryID & "'"
Set rstMB = CurrentDb.OpenRecordset(strSQLrMB)
With rstMB
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
While (Not .EOF)
If .Updatable Then
.Edit
rstMB!Subject = objMailItem.Subject
rstMB!Body = objMailItem.Body
Call subCategory(objMailItem)
rstMB!CSR = IIf(Len(objMailItem.Categories) = 0, "Unassigned", objMailItem.Categories)
rstMB!Importance = objMailItem.Importance
rstMB!Region = objMailItem.Parent
rstMB!DateModified = objMailItem.LastModificationTime
rstMB!FlagCompleted = objMailItem.FlagRequest
rstMB!folder = objMailItem.Parent
rstMB!Path = objMailItem
.Update
End If
.MoveNext
Wend
Else
rstMB.AddNew
rstMB!olid = objMailItem.EntryID
rstMB!ConversationIndex = objMailItem.ConversationIndex
rstMB!ConversationID = objMailItem.ConversationID
rstMB!Conversation = objMailItem.ConversationTopic
rstMB!To = Left(objMailItem.To, 250)
rstMB!CC = Left(objMailItem.CC, 250)
rstMB!Subject = objMailItem.Subject
rstMB!Body = objMailItem.Body
Call subCategory(objMailItem)
rstMB!CSR = IIf(Len(objMailItem.Categories) = 0, "Unassigned", objMailItem.Categories)
rstMB!Importance = objMailItem.Importance
rstMB!From = objMailItem.SenderEmailAddress
rstMB!Region = objMailItem.Parent
rstMB!DateReceived = objMailItem.ReceivedTime
rstMB!DateSent = objMailItem.SentOn
rstMB!DateCreated = objMailItem.CreationTime
rstMB!DateModified = objMailItem.LastModificationTime
rstMB!FlagCompleted = objMailItem.FlagRequest
rstMB!folder = objMailItem.Parent
rstMB.Update
End If
.Close
End With
ImportEmailItem_Exit:
Set rstMB = Nothing
Exit Sub
ImportEmailItem_Error:
Debug.Print Err.Number & " " & Err.Description
Select Case Err.Number
Case 91
Resume Next
Case 3022
Resume Next
Case -2147221233
MsgBox "Customer Care Account Name is incorrect, please enter the Mail box name as seen in your outlook client.", vbOKOnly, "Mail Folder Name Error"
Me.txtMailAccountName.SetFocus
Exit Sub
Case Else
MsgBox "Error #: " & Err.Number & " " & Err.Description '& Chr(13) + Chr(10) & IIf(mail.Subject Is Null, "", mail.Subject) & " " & IIf(mail.ReceivedTime Is Null, "", mail.ReceivedTime)
' DoCmd.RunSQL "INSERT INTO tblImportReport(ImportDate,ImportFolder,ImportResult,ImportEmailCount) VALUES (#" & Now() & "#,'" & mailFolder & "', 'Error " & Err.Number & "', " & dMailCount & ")"
Resume Next 'cmdImportEmail_Exit
End Select
End Sub
Is there a way to uniquely identify an email with a single field no matter whether it has been moved or not?
I have an idea of what I could do to make sure I have the right email and get the original entry in my database. If there was no other way I could concatenate fields together to form a unique field and then get the database table's primary key field value.
You can use the PR_SEARCH_KEY property (DASL name http://schemas.microsoft.com/mapi/proptag/0x300B0102) - it does not change when a message is moved. It can be accessed through MailItem.PropertyAccessor.GetProperty, but unfortunately you cannot use PT_BINARY properties in Items.Find/Restrict.
You can also set your own named property using MailItem.UserProperties.
UPDATE:
For PR_SEARCH_KEY, see https://msdn.microsoft.com/en-us/library/office/cc815908.aspx.
MaillItem.UserProperties can be used from anywhere - Outlook Object Model is Outlook Object Model whether it is used from inside Outlook or externally from Excel. Keep in mind that setting a user property and saving the item will change its last modified date.
If you want to stick to PR_SEARCH_KEY, to be be able to sort on it, you might want to look at Redemption (I am its author) - its RDOFolder.Items.Find / Restrict methods allow PT_BINARY properties in its queries, e.g. "http://schemas.microsoft.com/mapi/proptag/0x300B0102" = '89F75D48972B384EB2C50266D1541099'
Here is VBA code tested in MS Access 2013 to extract the PR_SEARCH_KEY from an Outlook.MailItem and convert to a string:
Public Function strGetMailItemUniqueId( _
olMailItem As Outlook.MailItem _
) As String
Dim PR_SEARCH_KEY As String
PR_SEARCH_KEY = "http://schemas.microsoft.com/mapi/proptag/0x300B0102"
Dim olPA As Outlook.PropertyAccessor
Set olPA = olMailItem.PropertyAccessor
Dim vBinary As Variant
vBinary = olPA.GetProperty(PR_SEARCH_KEY)
strGetMailItemUniqueId = olPA.BinaryToString(vBinary)
End Function
In Microsoft Outlook versions like 2007, 2010, Office 365 etc. there is a property Message-ID in the headers section of the email.
You can use this property to uniquely identify an email.

How to wait for an application to start in VBScript?

I'm a new VB Script programmer trying to use VB Script to open a pdf file via the default program (Adobe Reader X in this case) and save it as a text file.
The current script I have opens the PDF, waits 1 second, then saves it as text. However, for slower computers, it might take more than 1 second for the PDF to load up. Does anyone know how to do a sleep loop until the file is opened or the status is ready?
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run """C:\Temp\Gasprices.pdf"""
Set objShell = CreateObject("WScript.Shell")
wscript.sleep 1000
objShell.SendKeys "%FAX%S"
First off, since you are a beginner, always use Option Explicit. Many errors are caused by typos in variable names, you will catch them if you force yourself to declare all variables you use.
Secondly, you don't need to create two WScript.Shell objects, just re-use the existing one.
Thirdly, you need to activate the application you want to send commands to. That's what the Shell object's AppActivate method is for. It returns True or False, indicating whether bringing the application in question to the foreground has worked or not. You could use that in a loop (While Not Shell.AppActivate("Adobe Reader") ...) to wait exactly as long as the application needs.
However, the downside is that you need to know the exact title of the application window (or its process ID) for this to work at all. Application titles might change without warning, so this is kind of shaky. The PID is robust but it is not guessable.
In the end you will need the help of WMI to list all processes, fetch the correct PID and then pass that to AppActivate. The Win32_Process class is made for this.
Dim Shell, WMI, pid
Set Shell = WScript.CreateObject("WScript.Shell")
Set WMI = GetObject("winmgmts:!\\.\root\cimv2")
Shell.Run "start ""C:\Temp\Gasprices.pdf"""
pid = WaitForProcess("AcroRd32.exe", 5)
If pid > 0 Then
Shell.AppActivate pid
Shell.SendKeys "%FAX%S"
Else
WScript.Echo "Could not talk to PDF reader"
WScript.Quit 1
End If
Function WaitForProcess(imageName, tries)
Dim wql, process
wql = "SELECT ProcessId FROM Win32_Process WHERE Name = '" & imageName & "'"
WaitForProcess = 0
While tries > 0 And WaitForProcess = 0
For Each process In WMI.ExecQuery(wql)
WaitForProcess = process.ProcessId
Next
If WaitForProcess = 0 Then
WScript.Sleep 1000
tries = tries - 1
End If
Wend
End Function
Note that assigning to the function name (as in WaitForProcess = 0) sets the return value.
You could optimize this by finding the script's own PID and querying
"SELECT ProcessId FROM Win32_Process WHERE ParentProcessId = '" & scriptPID & "'"
in WaitForProcess().
Another possible option would be to test for Process CPU Usage...
You would need to test and see if this works in your environment...
Dim oShell, oExec, PID, X, Z
Set oShell = CreateObject("WScript.Shell")
Set oExec = oShell.Exec(Chr(34) & "C:\ADOBE PATH" & Chr(34) & " " & Chr(34) & "C:\YOUR PDF PATH.pdf" & Chr(34))
PID = oExec.ProcessID
WScript.Echo PID
'Prevent an Endless Loop
Z = 600 'about one minute worse case
Do
WScript.Sleep 100
X = GetCPUUsage(PID)
WScript.Echo X
Z = Z - 1
If oExec.Status <> 0 Then
MsgBox "The Process has been Terminated. Ending Script"
WScript.Quit
End If
Loop Until X = 0 Or Z = 0
If Z > 0 Then
WScript.Echo "Process Is More Or Less Opened"
Else
WScript.Echo "Process is open... Maybe?"
End If
Function GetCPUUsage(ProcID)
Dim objWMIService, colItems, objItem
Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20
'Just in case
GetCPUUsage = 0
Set objWMIService = GetObject("winmgmts:\\.\root\CIMV2")
Set colItems = objWMIService.ExecQuery("SELECT PercentProcessorTime FROM Win32_PerfFormattedData_PerfProc_Process WHERE IDProcess = '" & ProcID & "'", _
"WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
For Each objItem In colItems
GetCPUUsage = objItem.PercentProcessorTime
Next
End Function

Using the .Restrict method in Outlook VBA to filter on single recipient email address

I have code in Access that gets all emails in the user's Inbox that are sent by an individual email address. This code (simplified, below) works fine:
Dim outItems as Outlook.Items
Dim strEMAddress as string
Dim outFolder as Outlook.MAPIFolder
Set outFolder = outNS.GetDefaultFolder(olFolderInbox)
Set outItems = outFolder.Items
str="my#email.com"
Set outItems = outItems.Restrict("[SenderEmailAddress] = " & "'" & strEMAddress & "'")
I am looking for something that will do likewise on the SentMails folder, restricting the items to those sent to a specific email address.
I know this is complicated by the fact that .Recipients is a collection (as items can/do have more than one recipient). I am hoping there is a way to return a list of items that contain the email address I am looking for in any of the sent fields (To/CC/bcc - but happy with just To if this is easier).
I have searched online and found .To is no good (is not the email address) and I can't get pseudo code such as this work:
Set outItems = outItems.Restrict("[Recipients] = " & "'" & strEMAddress & "'")
You can use the DASL query as the filter string in your items.restrict method.
For example to find all mails i sent to Ali Raza i use the following
str_fltr = "#SQL=""urn:schemas:httpmail:displayto"" ci_phrasematch '%Ali Raza%'"
The good thing about the above DASL query is that it returns matches with multiple recepients whether if you use the jet syntax for searching resultx will only contain items with one recipient. Jet syntax is the one that you are currently using. You should use the [To] property rather than [Recipients]
Here https://msdn.microsoft.com/en-us/library/cc513841%28v=office.12%29.aspx#SearchingOutlookData_Overview is good place where you can learn almost everything about searching in outlook.
Here http://www.msoffice.us/Outlook/PDF/%28Outlook%202010%29%20Common%20DASL%20Property%20Tags.pdf is a list of common DASL tags which will come in handy if you get a grip on DASL syntax.
For multiple [TO/CC/BCC] filter example would be...
Option Explicit
Public Sub Example()
Dim olNs As Outlook.NameSpace
Dim Folder As Outlook.MAPIFolder
Dim Items As Outlook.Items
Dim Filter As String
Dim Msg As String
Dim i As Long
Set olNs = Application.GetNamespace("MAPI")
Set Folder = olNs.GetDefaultFolder(olFolderSentMail)
Filter = "#SQL=" & "urn:schemas:httpmail:displayto" & _
" Like '%John Doe%' Or " & _
"urn:schemas:httpmail:displaycc" & _
" Like '%John Doe%' Or " & _
"urn:schemas:httpmail:displaybcc" & _
" Like '%John Doe%'"
Set Items = Folder.Items.Restrict(Filter)
Msg = Items.Count & " Items in " & Folder.Name & " Folder"
If MsgBox(Msg, vbYesNo) = vbYes Then
For i = Items.Count To 1 Step -1
Debug.Print Items(i) 'Immediate Window
Next
End If
End Sub
now remember if the display name is John.Doe#Email.com then filter should be %John.Doe#Email.com% else use %John Doe%
If using Redemption is an option (I am its author), you can use RDOFolder.Items.Restrict - unlike Outlook Object Model, it does expand To/CC/BCC queries into recipient sub restrictions on PR_DISPLAY_NAME and PR_EMAIL_ADDRESS properties on each recipient (RES_SUBRESTRICTION / PR_MESSAGE_RECIPIENTS / RES_OR / PR_DISPLAY_NAME | PR_EMAIL_ADDRESS).
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Folder = Session.GetFolderFromID(Application.ActiveExplorer.CurrentFolder.EntryID)
set restrItems = Folder.Items.Restrict(" TO = 'user#domain.demo' ")
You can also specify Recipients property in the SQL query - it will be matched against recipients of all types (to/cc/bb):
set restrItems = Folder.Items.Restrict(" Recipients = 'user#domain.demo' ")