A WQL query in WMI using the COM API returns a SWbemObjectSet. From this, we can use the ItemIndex method to iterate over all objects in the set, resulting in many SWbemObjects. These have a Properties_ property, which returns a SWbemPropertySet.
This object has a Count property, but no ItemIndex method, instead only an Item method, which takes a string name of the property to fetch. I'd like to iterate over all available properties, thus I don't have the name available. I've tried calling an ItemIndex method, but (as per the documentation) it does not exist on this object. There are many VBScript documents around that do something like For Each prop in object.Properties_, so I assume this is somehow possible.
How can I find all properties?
First, the ItemIndex property only exist in some versions of the Microsoft WMI Scripting Library you can find more info about this topic on this article which I wrote some time ago
Be careful when you import the Microsoft WMI Scripting Library (The code uses Delphi but the same applies to any language using this Library).
Now to iterate over the elements of an SWbemPropertySet you must get a instance to the enumerator accessing the _NewEnum property which provides a method for enumerating a collection of variants using the IEnumVARIANT interface. This is done automagically by vbscript, but in others languages like Delphi or C++ must be done manually.
Try this Delphi sample
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants,
WbemScripting_TLB in 'WbemScripting_TLB.pas';
procedure ShowProperties(const WMINameSpace, WMIClass : string);
var
WMIServices : ISWbemServices;
SWbemObjectSet : ISWbemObjectSet;
SObject : ISWbemObject;
LProperty : ISWbemProperty;
Enum, Enum2 : IEnumVariant;
TempObj, TempObj2: OleVariant;
Value : Cardinal;
SWbemPropertySet: ISWbemPropertySet;
begin
WMIServices := CoSWbemLocator.Create.ConnectServer('.', WMINameSpace,'', '', '', '', 0, nil);
SWbemObjectSet := WMIServices.ExecQuery(Format('Select * FROM %s',[WMIClass]), 'WQL', 0, nil);
Enum := (SWbemObjectSet._NewEnum) as IEnumVariant;
if (Enum.Next(1, TempObj, Value) = S_OK) then
begin
SObject := IUnknown(TempObj) as ISWBemObject;
SWbemPropertySet := SObject.Properties_;
Enum2 := (SWbemPropertySet._NewEnum) as IEnumVariant;
while (Enum2.Next(1, TempObj2, Value) = S_OK) do
begin
LProperty := IUnknown(TempObj2) as ISWbemProperty;
Writeln(LProperty.Name);
TempObj2:=Unassigned;
end;
TempObj:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
ShowProperties('root\cimv2','Win32_BaseBoard');
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
I hope which you can translate this sample to the Go language.
You need to query the object class to get all the properties. Here's an example where the object class properties are used to convert a WMI query to a stringified JSON object.
function getWMIObjectJSON(strNameSpace,strClass,strFilter)
strComputer = "."
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\" & strNameSpace)
Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\" & strNameSpace & ":" & strClass)
colCount=0
rtn="{""description"":""" & escStr(strNameSpace & ":" & strClass & " " & strFilter) & """,""title"":""" & _
escStr(strNameSpace & ":" & strClass) & """,""columns"":["
For Each objClassProperty In objClass.Properties_
if colCount>0 then rtn=rtn & ","
rtn=rtn & """" & escStr(objClassProperty.Name) & """"
colCount=colCount + 1
Next
Set colData = objWMIService.ExecQuery("Select * from " & strClass & " " & strFilter)
rtn=rtn & "],""record_count"":" & colData.Count & ",""records"":["
rowCount=0
For Each objData In colData
if rowCount > 0 then rtn=rtn & ","
rtn=rtn & "{"
colCount=0
For Each objClassProperty In objClass.Properties_
if colCount > 0 then rtn=rtn & ","
rtn=rtn & """" & escStr(objClassProperty.Name) & """:""" & _
escStr(objData.Properties_(objClassProperty.Name).Value) & """"
colCount=colCount + 1
Next
rtn=rtn & "}"
rowCount=rowCount + 1
Next
rtn=rtn & "]}"
getWMIObjectJSON = rtn
end function
desktop = getWMIObjectJSON("root\cimv2","Win32_Desktop","where Name like '%u%'")
WScript.Echo desktop
<package>
<job id="wmiExample">
<script language="VBScript" src="wmi.vbs"/>
<script language="JScript">
var desktop = eval("(" + getWMIObjectJSON("root\cimv2","Win32_Desktop","") + ")");
for(d in desktop.records){
WScript.Echo(desktop.records[d].Name);
}
</script>
</job>
</package>
Related
I have a VB.net program that I am trying to add a bitlocker lookup tool that will search active directory for the machine name, and display the "Password ID" as well as the "Recovery Password"
So far my script/code works flawlessly for the lookup and displaying the Recovery Password, but I cannot get it to display the Password ID.
I've tried:
Item.Properties("msFVE-RecoveryGuid")(0)
Which returns the error "System.InvalidCastException: Conversion from type 'Byte()' to type 'String' is not valid."
Item.Properties("msFVE-RecoveryGuid")(0).ToString
Which returns "System.Byte[]"
Item.Properties("msFVE-RecoveryGuid").ToString
Which returns "System.DirectoryServices.ResultPropertyValueCollection"
So far in my searching I've only seen C# examples, and I haven't been able to translate.
The same for Recovery Password works however:
(Item.Properties("msFVE-RecoveryPassword")(0))
Here is the larger snippet of what I have for context:
Dim RootDSE As New DirectoryEntry("LDAP://RootDSE")
Dim DomainDN As String = RootDSE.Properties("DefaultNamingContext").Value
Dim ADsearch As New DirectorySearcher("LDAP://" & DomainDN)
ADsearch.Filter = ("(&(objectClass=computer)(name=" & MachineName & "))")
Dim ADresult As SearchResult = ADsearch.FindOne
Dim ADpath As String = ADresult.Path
Dim BTsearch As New DirectorySearcher()
BTsearch.SearchRoot = New DirectoryEntry(ADpath)
BTsearch.Filter = "(&(objectClass=msFVE-RecoveryInformation))"
Dim BitLockers As SearchResultCollection = BTsearch.FindAll()
Dim Item As SearchResult
Dim longTempstring As String = ""
For Each Item In BitLockers
If Item.Properties.Contains("msFVE-RecoveryGuid") Then
Dim tempstring As String = Item.Properties("msFVE-RecoveryGuid")(0).ToString
longTempstring = longTempstring & tempstring & vbNewLine
'ListBox2.Items.Add(Item.Properties("msFVE-RecoveryGuid")(0))
End If
If Item.Properties.Contains("msFVE-RecoveryPassword") Then
ListBox1.Items.Add(Item.Properties("msFVE-RecoveryPassword")(0))
End If
Next
MsgBox(longTempstring)
So I figured out that I needed to convert the bytes to hex in order to get them to match what is viewed in the Microsoft Management Console. Once I began doing that the only problem I ran into is that I discovered the indexing of the byte arrays are not in the same order as they are in Active Directory. -- so instead of looping I had to list out each index of the Byte array and sort them to their proper positions so that they match how they show up in AD.
My end function is:
Function bitread(ByVal GUID As Byte())
Dim tempVar As String
tempVar = GUID(3).ToString("X02") & GUID(2).ToString("X02") _
& GUID(1).ToString("X02") & GUID(0).ToString("X02") & "-" _
& GUID(5).ToString("X02") & GUID(4).ToString("X02") & "-" _
& GUID(7).ToString("X02") & GUID(6).ToString("X02") & "-" _
& GUID(8).ToString("X02") & GUID(9).ToString("X02") & "-" _
& GUID(10).ToString("X02") & GUID(11).ToString("X02") _
& GUID(12).ToString("X02") & GUID(13).ToString("X02") _
& GUID(14).ToString("X02") & GUID(15).ToString("X02")
Return tempVar
End Function
Called with:
bitread(Item.Properties("msFVE-RecoveryGUID")(0))
we are generating a mass of documents very dynamically. Therefore we concatenate source code and build a dll at runtime. This is running since windows XP.
Now we are in tests of windows 10 and it fails compiling this dll with the error "BC31019: Unable to write to output file 'C:\Users[name]AppData\Local\Temp\xyz.dll': The specified image file did not contain a resource section"
For testing purposes we remove all generated source code and replace it by a rudimental class with only one function (throwing an exception with specified text) and no referenced assemblies.
This is also running on all machines except windows 10. Same error.
Can anybody guess why?
This is the rudimental method
Public Sub Compile()
Dim lSourceCode = "Namespace DynamicOutput" & vbCrLf &
" Public Class Template" & vbCrLf &
" Sub New()" & vbCrLf &
" End Sub" & vbCrLf &
" Public Sub Generate(ByVal spoolJob As Object, ByVal print As Object)" & vbCrLf &
" Throw New System.Exception(""Generate reached"")" & vbCrLf &
" End Sub" & vbCrLf &
"" & vbCrLf &
" End Class" & vbCrLf &
"End Namespace"
Dim lParams As CodeDom.Compiler.CompilerParameters = New CodeDom.Compiler.CompilerParameters
lParams.CompilerOptions = "/target:library /rootnamespace:CompanyName /d:TRACE=TRUE /optimize "
lParams.IncludeDebugInformation = True
lParams.GenerateExecutable = False
lParams.TreatWarningsAsErrors = False
lParams.GenerateInMemory = True
Dim lProviderOptions As New Dictionary(Of String, String) From {{"CompilerVersion", "v4.0"}}
Dim lResult As CodeDom.Compiler.CompilerResults = Nothing
Using provider As New VBCodeProvider(lProviderOptions)
lResult = provider.CompileAssemblyFromSource(lParams, lSourceCode)
End Using
' ... check for errors
Dim lInstance As Object = lResult.CompiledAssembly.CreateInstance("CompanyName.DynamicOutput.Template")
lInstance.GetType.GetMethod("Generate").Invoke(lInstance, New Object() {Me.SpoolJob, Me.Print})
End Sub
here is a portion of the code:
oFSO.DeleteFolder Environ("C:\Users\%USERNAME%\AppData\Local\Temp") & "\* " & oFSO.GetFile(strZipFile).Name, True
when i try to execute it it gives me this error : "Path not found"
Use
oFSO.DeleteFolder _
Environment.ExpandEnvironmentVariables("C:\Users\%USERNAME%\AppData\Local\Temp") & _
......
or use a complicated string concatenation (without the % around the environment variable)
oFSO.DeleteFolder _
"C:\Users\" & Environ("USERNAME") & "\AppData\Local\Temp") & "\* " ....
However when dealing with this kind of paths, the best approach is to use Environment class
Dim userData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
Dim tempFolder = Path.Combine(userData, "temp")
Now the rest of your path seems to be a bit wrong.
"* " (a space after the wild card?) followed by a filename doesn't seems to be correct)
I need to send an email with a table that has variable values in each cell. I can do this using any method (html via email, an excel/word table, etc.). The only hitch is due to the restrictions of the Emailer program and System.Net.Mail import, it has to be a string.
Here's what I have so far:
Imports DelayEmailer.DelayTrackerWs
Imports System.Configuration
Public Class DelayEmailer
Public Shared Sub Main()
Dim ws As New DelayTrackerWs.DelayUploader
Dim delays As DelayTrackerWs.Delay()
Dim emailer As New Emailer()
Dim delaystring As String
delays = ws.SearchDelaysDate(DelayTrackerWs.AreaEnum.QT, DelayTrackerWs.UnitEnum.QT, Now.AddDays(-1), Now)
delaystring = "Delays" & vbNewLine
delaystring &= "Facilty Start Time Status Category Reason Comment"
For i = 0 To delays.Length - 1
delaystring &= vbNewLine & delays(i).Facility & " "
delaystring &= FormatDateTime(delays(i).DelayStartDateTime, DateFormat.ShortDate) & " "
delaystring &= FormatDateTime(delays(i).DelayStartDateTime, DateFormat.ShortTime) & " "
'delaystring &= delays(i).DelayDuration & " "
delaystring &= delays(i).Status & " "
delaystring &= delays(i).CategoryCode & " "
delaystring &= delays(i).ReasonCode & " "
delaystring &= delays(i).Comment
Next
emailer.Send(ConfigurationManager.AppSettings("EmailList"), "delays", delaystring)
End Sub
As you can see, I currently have just a bunch of concatenated strings that line up if the values of each delays(i) are the same. The other problem is that this needs to be easily viewable via mobile devices and with the strings, it wraps and gets really unreadable. A table here should fix this.
You can send html email from .NET using MailMessage and SmtpClient classes, create an email template as string and set MailMessage's IsBodyHtml property to true:
Dim strHeader As String = "<table><tbody>"
Dim strFooter As String = "</tbody></table>"
Dim sbContent As New StringBuilder()
For i As Integer = 1 To rows
sbContent.Append("<tr>")
For j As Integer = 1 To cols
sbContent.Append(String.Format("<td>{0}</td>", YOUR_TD_VALUE_STRING))
Next j
sbContent.Append("</tr>");
Next i
Dim emailTemplate As String = strHeader & sbContent.ToString() & strFooter
...
I'm trying to use an arraylist as the parameter to String.Format.
msg = msg & String.Format("<td>{0}</td>" & _
"<td>{1}</td>" & _
"<td>{2}</td>" & _
"<td>{3}</td>" & _
"<td>{4}</td>" & _
"<td>{5}</td>" & _
"<td>{6}</td>" & _
"<td>{7}</td>" & _
"<td>{8}</td>", param)
where param is an ArrayList and the contents are thus (copied from watch list):
+ (0) 9 {Integer} Object
+ (1) 3 {Integer} Object
+ (2) 5 {Integer} Object
+ (3) "180" {String} Object
+ (4) 0D {Decimal} Object
+ (5) 6.788D {Decimal} Object
+ (6) #3/13/2009# {Date} Object
+ (7) "2004" {String} Object
+ (8) "" {String} Object
But this code throws a FormatException
Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
Am I wrong that it's possible to use an arraylist? If it is possible, any clues as to why it would be throwing such an error?
Thanks
Does it accept an ArrayList?
Did you try:
"<td>{8}</td>", param.ToArray())
You probably need to pass in an object array and not an ArrayList. If you change the code as such you may see what is going wrong:
msg = msg & String.Format("<td>{0}</td>", param)
It should print something like
< td>System.ArrayList< td>
Have you tried this ?
msg = msg & String.Format("<td>{0}</td>" & _
"<td>{1}</td>" & _
"<td>{2}</td>" & _
"<td>{3}</td>" & _
"<td>{4}</td>" & _
"<td>{5}</td>" & _
"<td>{6}</td>" & _
"<td>{7}</td>" & _
"<td>{8}</td>", param.ToArray())