Poor performance on SELECT * FROM Win32_Printer - vb.net

I'm updating some routines that help identify and store the printers available for a user when they access our application over Citrix. When the printers are created they are flagged with a name like;
In citrix;
PrinterName (from MyTerminalID) in session 209
In RDP;
PrinterName (redirected 209)
run locally we just get the PrinterName
That's all fine but we've switched from using the "system.drawing" method of identifying printers which is somewhat dangerous and identifies every single printer on the citrix node to using the System.Management query SELECT * FROM Win32_Printer
I've modified this with a condition e.g., DeviceID Like '%(from MyTerminalID)%' AND DeviceID LIKE '%in session 123' but the first time I execute the query it can take a number of seconds (15+) before it comes back with the results. One I have the results it's very quick.
Now the question is, how can I make it go faster ?
I'm only interesting in getting the subset of printers that either relate to myTerminalID AND my session or my session (if RDP, but the citrix version is the important bit).
Is the query running like a proper SQL query so will use the condition to reduce the time to run or is it more of a pseudo query where it's going to pull back the whole data set and then apply the filtering.
Is there a way to reduce the amount of data that it needs to go and get (e.g., if I do a SELECT DeviceID rather than a SELECT *).
re-running the query is much faster <1 second. In some ways it doesn't matter if it takes 15+ seconds to pull back the list of printers as that's currently used for set up but there is also another routine that checks if the previously selected printers are still available to the user on the same terminal (even if a different user was the one that set them up). I would also like to try and potentially filter out "non-printers" like the fax etc., and to do that I need to look at some of the detail of the properties.

About your current scenario, you could try to set two policies:
- Use Universal printing only
- Do not auto-create client printers
then force the update of the group policy (client side).
Maybe you want that client and server printers mirror each other, but just for testing. (Note, however, that I've seen much worse than 15 seconds in WMI enumerations in different situations. Note also that I'm no expert in Citrix Sessions/UPS.)
WMI SQL (WQL) is a subset of the ANSI SQL standard. See:
Querying with WQL - WQL (SQL for WMI) Keywords
Of course it returns a set of records (objects) that match the constraints defined in the query. Is it an efficient system? You may hear very different opinions on this matter.
The first query is slower because new objects are created at this time.
If not otherwise instructed, WMI caches these objects for subsequent queries.See EnumerationOptions.Rewindable Property
About filtering the results, see Use the Like Operator to Simplify Your WQL Queries
In the test that I made, I use this filter to rule out "non-printers" printers:
_Query.Condition = "NOT DeviceID LIKE '%fax%' AND NOT DeviceID LIKE '%xps%' AND NOT DeviceID LIKE '%PDF%'"
The following test has been performed on a machine that has 7 printers, 3 of which are "non-printers": Fax, XPS Driver and PDF Driver. The other 4 are "real" printers: 2 are local and 2 are network printers.
QUERY MIN MAX
One Printer: 96ms - 181ms
All Printers: (7) 107ms - 190ms
Filtered: (4) 108ms - 229ms
50 iterations measured using a StopWatch
Dim _SW As Stopwatch = Stopwatch.StartNew()
Dim _Printers As List(Of Printer) = GetSystemPrinters()
Console.WriteLine(_SW.ElapsedMilliseconds.ToString())
This is the code and object used:
Public Function GetSystemPrinters() As List(Of Printer)
Dim _Printers As New List(Of Printer)()
Dim _ConnOptions As New ConnectionOptions()
_ConnOptions.Authentication = AuthenticationLevel.Connect
_ConnOptions.Impersonation = ImpersonationLevel.Impersonate
'If needed => .UserName, .Password, .Authority
_ConnOptions.Timeout = EnumerationOptions.InfiniteTimeout
Dim _Scope As New ManagementScope("\\" + Environment.MachineName + "\root\CIMV2", _ConnOptions)
_Scope.Connect()
Dim _Query As New SelectQuery("SELECT * FROM Win32_Printer") 'Or "SELECT * FROM CIM_Printer"
'Create a filter to rule out some "non-printers"
_Query.Condition = "NOT DeviceID LIKE '%fax%' AND NOT DeviceID LIKE '%xps%' AND NOT DeviceID LIKE '%PDF%'"
Dim _Options As New EnumerationOptions()
_Options.Timeout = EnumerationOptions.InfiniteTimeout
'Forward only query => no caching
_Options.Rewindable = False
'Pseudo-async result
_Options.ReturnImmediately = True
Dim _searcher As New ManagementObjectSearcher(_Scope, _Query, _Options)
For Each _objPrinter As ManagementObject In _searcher.Get()
Dim _Printer As New Printer()
_Printer.PrinterName = _objPrinter.Properties("Name").Value.ToString()
_Printer.PrinterPort = _objPrinter.Properties("PortName").Value.ToString()
_Printer.PrinterDriver = _objPrinter.Properties("DriverName").Value.ToString()
_Printer.PrintProcessor = _objPrinter.Properties("PrintProcessor").Value.ToString()
_Printer.DeviceID = _objPrinter.Properties("DeviceID").Value.ToString()
_Printer.Status = CType(_objPrinter.Properties("PrinterStatus").Value, PrinterStatus)
_Printer.IsLocalPrinter = If(_objPrinter.Properties("Local").Value.ToString() = "True", True, False)
_Printer.IsNetworkPrinter = If(_objPrinter.Properties("Network").Value.ToString() = "True", True, False)
_Printer.IsDefaultPrinter = If(_objPrinter.Properties("Default").Value.ToString() = "True", True, False)
Dim _PrinterProps As New List(Of PrinterProperties)()
For Each _oBJData As PropertyData In _objPrinter.Properties
_PrinterProps.Add(New PrinterProperties() With {
.PropertyName = _oBJData.Name,
.PropertyValue = If(_oBJData.Value IsNot Nothing, _oBJData.Value.ToString(), ""),
.PropertyValueType = _oBJData.Type.ToString(),
.PropertyIsArray = _oBJData.IsArray
})
Next
_Printer.PrinterProperties = _PrinterProps
_Printers.Add(_Printer)
Next
Return _Printers
End Function
Relative Objects:
Public Enum PrinterStatus As Integer
Other = 1
Unknown = 2
Ready = 3
Printing = 4
Warmup = 5
StoppedPrinting = 6
Offline = 7
End Enum
Public Class Printer
Public Property PrinterName() As String
Public Property PrinterPort() As String
Public Property PrinterDriver() As String
Public Property PrintProcessor() As String
Public Property DeviceID() As String
Public Property Status() As PrinterStatus
Public Property IsDefaultPrinter() As Boolean
Public Property IsLocalPrinter() As Boolean
Public Property IsNetworkPrinter() As Boolean
Public Property PrinterProperties() As List(Of PrinterProperties)
End Class
Public Class PrinterProperties
Public Property PropertyName() As String
Public Property PropertyValue() As String
Public Property PropertyValueType() As String
Public Property PropertyIsArray() As Boolean
End Class

Related

Back tic in VB.net

I am wondering if there is a way to use a back tic in vb.net ?
I am using N1QL to query a Couchbase db and i have a reserved keyword in my couchbase docs which is number. In N1QL you just enclose that word in back tic and that allows you to use reserved keywords.
Dim qRequest = New QueryRequest()
qRequest.Statement("select meta().id as DocId, _id,_type,`number`,cname_text,status,action from Vodex where _type ='cname'")
even so i dont get an error i dont get the desired response from my vb.code
I created a bucket called Vodex with a primary index and one document in it like this:
key: doc1
{
"number": 1
}
I then wrote a small console app. It's been a while since I've written VB.NET, so forgive this clumsy example:
Imports Couchbase
Imports Couchbase.Configuration.Client
Imports Couchbase.N1QL
Module Program
Sub Main(args As String())
Dim clientConfig = New ClientConfiguration()
Dim uris = New List(Of Uri)
uris.Add(New Uri("http://localhost:8091"))
clientConfig.Servers = uris
Dim cluster = New Cluster(clientConfig)
cluster.Authenticate("Administrator", "password")
Dim bucket = cluster.OpenBucket("Vodex")
Dim qRequest = New QueryRequest()
qRequest.Statement("select meta().id As DocId, `number` from Vodex")
Dim results = bucket.Query(Of Foo)(qRequest)
Dim rows = results.Rows
For Each o As Foo In rows
Console.WriteLine(o.DocId)
Console.WriteLine(o.Number)
Next
cluster.Dispose()
End Sub
End Module
Friend Class Foo
Public Property DocId As String
Public Property Number As Integer
End Class
After running that, "doc1" and "1" are printed to console. It seems to work okay, even with backticks. If you're experiencing another issue, it is probably not because of the backticks.

Create or clear Hashtable instance for each new request of Report in SSRS

I have a report done via VS2008 SSRS that is uploaded to my Reporting Services Server, that report is called when the event GenerateReport is called (on button click)
Now, I have the following code:
Public Shared Dim gruposVolTotal As New System.Collections.HashTable()
Public Shared Dim gruposSeleccion As New System.Collections.HashTable()
Public Shared Dim gruposModVenta As New System.Collections.HashTable()
Dim grupoActual as String = ""
Public Shared Dim acumuladoSeleccion as Double = 0
Public Shared Dim acumuladoVolTotal as Double = 0
Public Shared Dim acumuladoModVenta as Double = 0
Public Shared Dim acumuladoAnterior as Double = 0
Function Acumulador(ByVal MiGrupo as String, ByVal value as Double, ByVal Campo as String) As Double
If (GrupoActual <> MiGrupo) Then
If (Campo = "ModVenta") then
If (not gruposModVenta.Contains(MiGrupo)) then
acumuladoModVenta =acumuladoModVenta + value
gruposModVenta.Add(MiGrupo,acumuladoModVenta )
Return acumuladoModVenta
Else
Return gruposModVenta(MiGrupo)
End If
End If
If (Campo = "RunningValue") then
acumuladoVolTotal=acumuladoVolTotal + value
If (not gruposVolTotal.Contains(MiGrupo)) then
acumuladoAnterior = acumuladoVolTotal + acumuladoAnterior
gruposVolTotal.Add(MiGrupo, acumuladoAnterior)
End If
Return gruposVolTotal(MiGrupo)
End If
If (Campo = "VolumenSeleccion") then
If(not gruposSeleccion.Contains(MiGrupo)) then
acumuladoSeleccion=acumuladoSeleccion + value
gruposSeleccion.Add(MiGrupo, acumuladoSeleccion)
Return acumuladoSeleccion
Else
return gruposSeleccion(MiGrupo)
End If
End If
End If
End Function
What is the problem with this:
If you execute this on VS2008 the results are correct if I change pages (remember on VS2008) the elements persists and that is good.
If you execute this via web application, the first time will execute correctly but the second time if you choose different parameters on the report the values will stay as the 1st time you execute it.
This is because the variables and elements of the hashtables are saved in session so until I dont close the webpage and enter it again the values of these Hashtables and variables will stay the same after the first time you execute it.
If you change the scope of the variables and Hashtables to public, private or directly dim (private) each request (change of pages aka break page) will reset the value of the variables and hashtables.
So the Idea is to utilize the code I posted but what Im asking if is there any way to reset or clear the elements of the hashtables each time I call the report and change the parameters.
I saw in a forum that another user created a new hashtable for each request but those Hashtables wont be garbage collected and that means a lot of memory usage.
Let me know if you need more information.
EDIT:
You change the pages via ReportViewer Control.
I tried using the following code:
Protected Overrides Sub OnInit()
gruposVolTotal.Clear()
gruposSeleccion.Clear()
gruposModVenta.Clear()
grupoActual = ""
acumuladoSeleccion = 0
acumuladoVolTotal = 0
acumuladoModVenta = 0
acumuladoAnterior = 0
End Sub
But it seems it deletes it for every change of page and not each time I open the report.

Threading Exception: The number of WaitHandles must be less than or equal to 64

The title is to make this easy to find for others having this error. I'm new to Threading, so this is really giving me heck. I'm getting this runtime error that crashed Cassini. This is code that I'm maintaining originally developed as a website project in VS 2003 and converted to a VS 2008 website project.
Important Info:
The number of objects in the manualEvents array is 128 in this case.
products is an array of Strings
Need to support .NET 2.0
For Each product As String In products
If Not product.Trim().ToUpper().EndsWith("OBSOLETE") Then
calls += 1
End If
Next
Dim results(calls - 1) As DownloadResults
'Dim manualEvents(calls - 1) As Threading.ManualResetEvent '128 objects in this case.
Dim manualEvents(0) As Threading.ManualResetEvent
manualEvents(0) = New Threading.ManualResetEvent(False)
'NOTE: I don't think this will work because what is not seen here, is that
' this code is being used to populate and cache a long list of products,
' each with their own category, etc. Am I misunderstanding something?
'initialize results structures
'spawn background workers
calls = 0
For Each product As String In products
If Not product.Trim().ToUpper().EndsWith("OBSOLETE") Then
Dim result As New DownloadResults
'manualEvents(calls) = New Threading.ManualResetEvent(False)
'Moved above For Each after declaration of variable
result.params.product = product
result.params.category = docType
'result.ManualEvent = manualEvents(calls)
result.ManualEvent = manualEvents(0)
result.Context = Me._context
results(calls) = result
Threading.ThreadPool.QueueUserWorkItem(AddressOf ProcessSingleCategoryProduct, results(calls))
Threading.Interlocked.Increment(calls) 'Replaces below incrementation
'calls += 1
End If
Next
Threading.WaitHandle.WaitAll(manualEvents) 'CRASHES HERE
Thread Helper Function (for the sake of completion)
Public Shared Sub ProcessSingleCategoryProduct(ByVal state As Object)
Dim drs As DownloadResults = CType(state, DownloadResults)
Dim adc As New cADCWebService(drs.Context)
drs.docs = adc.DownloadADC(drs.params.category, drs.params.product)
drs.ManualEvent.Set()
End Sub
You don't need an array of 128 manual events to check for completion of all 128 threads.
Create only one manual reset event and a plain integer starting at 128. Decrement that integer using Interlocked.Decrement at the end of ProcessSingleCategoryProduct, and only signal the event when the count reaches zero:
if (Interlocked.Decrement(ByRef myCounter) = 0) myEvent.Set();
Then declare only one Threading.ManualResetEvent as opposed to an array of them, and you can call WaitOne instead of WaitAll on it, and you are done.
See also usr's comment for an easier alternative in case you have .NET 4.

Dynamic query Linq to xml VB.NET

Hey,
I want to write a query that the "where" in the query is a string something like"
Dim query as string= "Name =xxxx and Date > 10 "
Dim t = from book in doc.Descendants("books") Select _
[Name] = book..value, [Date] = book..value....
Where (query)
I build the query string on run time
Thanks...
I'm not saying this is your case but I see this a lot from people that came from ASP classic where we used to build dynamic SQL strings all of the time. We tend to hope that LINQ will give us some more power in part of the code but let us use strings elsewhere. Unfortunately this isn't true. Where takes a boolean argument and there's no way around that. You can write your own parser that uses reflection and eventually returns a boolean but you'd be writing a lot of code that could be error prone. Here's how you really should do it:
Assuming this is our data class:
Public Class TestObject
Public Property Name As String
Public Property Job As String
End Class
And here's our test data:
Dim Objects As New List(Of TestObject)
Objects.Add(New TestObject() With {.Name = "A", .Job = "Baker"})
Objects.Add(New TestObject() With {.Name = "B", .Job = "President"})
Objects.Add(New TestObject() With {.Name = "C", .Job = "Bus Driver"})
Objects.Add(New TestObject() With {.Name = "D", .Job = "Trainer"})
What you want to do is create a variable that represents the data to search for:
''//This variable simulates our choice. Normally we would be parsing the querystring, form data, XML values, etc
Dim RandNum = New Random().Next(0, 3)
Dim LookForName As String = Nothing
Select Case RandNum
Case 0 : LookForName = "A"
Case 1 : LookForName = "B"
Case 2 : LookForName = "C"
End Select
''//Query based on our name
Dim Subset = (From O In Objects Select O Where (O.Name = LookForName)).ToList()
If sometimes you need to search on Job and sometimes and sometimes you don't you just might have to write a couple of queries:
Dim Subset As List(Of TestObject)
Select Case RandNum
Case 0
Subset = (From O In Objects Select O Where (O.Name = "A" And O.Job = "Baker")).ToList()
Case Else
Select Case RandNum
Case 1 : LookForName = "B"
Case 2 : LookForName = "C"
End Select
Subset = (From O In Objects Select O Where (O.Name = LookForName)).ToList()
End Select
And just to explain writing your own query parser (which is a path that I recommend you DO NOT go down), here is a very, very, very rough start. It only supports = and only strings and can break at multiple points.
Public Shared Function QueryParser(ByVal obj As Object, ByVal ParamArray queries() As String) As Boolean
''//Sanity check
If obj Is Nothing Then Throw New ArgumentNullException("obj")
If (queries Is Nothing) OrElse (queries.Count = 0) Then Throw New ArgumentNullException("queries")
''//Array of property/value
Dim NameValue() As String
''//Loop through each query
For Each Q In queries
''//Remove whitespace around equals sign
Q = System.Text.RegularExpressions.Regex.Replace(Q, "\s+=\s+", "=")
''//Break the query into two parts.
''//NOTE: this only supports the equal sign right now
NameValue = Q.Split("="c)
''//NOTE: if either part of the query also contains an equal sign then this exception will be thrown
If NameValue.Length <> 2 Then Throw New ArgumentException("Queries must be in the format X=Y")
''//Grab the property by name
Dim P = obj.GetType().GetProperty(NameValue(0))
''//Make sure it exists
If P Is Nothing Then Throw New ApplicationException(String.Format("Cannot find property {0}", NameValue(0)))
''//We only support strings right now
If Not P.PropertyType Is GetType(String) Then Throw New ApplicationException("Only string property types are support")
''//Get the value of the property for the supplied object
Dim V = P.GetValue(obj, Nothing)
''//Assumming null never equals null return false for a null value
If V Is Nothing Then Return False
''//Compare the two strings, return false if something doesn't match.
''//You could use String.Compare here, too, but this will use the current Option Compare rules
If V.ToString() <> NameValue(1) Then Return False
Next
''//The above didn't fail so return true
Return True
End Function
This code would allow you to write:
Dim Subset = (From O In Objects Select O Where (QueryParser(O, "Name = A", "Job = Baker"))).ToList()
No, there is nothing directly like what you're looking for where you can pass in a string. As they say, when all you have is a hammer, everything looks like a nail...The real problem is that you need to learn what LINQ is good at and apply it to your code (if it is a good fit), rather than try and make it do what you could with a dynamically built SQL query string.
What you should probably be doing is making those "Where" clauses strongly typed anyway. Your current code has a lot of potential to blow up and be hard to debug.
What you could do instead is something like this (sorry, using C#, been a while since I've touched VB.NET):
var query = from book in doc.Descendants("books")
select book;
if(needsNameComparison)
{
query = query.where(book.Name == nameToCompare);
}
if(needsDateComparison)
{
query = query.Where(book.Date > 10);
}
List<book> bookList = query.ToList();
With LINQ, "query" isn't actually run until the "ToList()" call. Since it uses late execution, the query is dynamic in that it's being built on until it actually needs to run. This is similar to the code you were looking to use before since you were building a query string ahead of time, then executing it at a specific point.

FileHelpers Library - Append Multiple Records on Transform

Using the FileHelpers library to do some great things in VB.NET. Parsing files with dynamic classes built from text file templates. One thing I can't find: A way to read in a single record and determine that it should result in the generation of two records.
Current Code:
Dim FromType As Type = Dynamic.ClassBuilder.ClassFromSourceFile(MyFilePath, MyDynamicTypeName, NetLanguage.VbNet)
Dim FromRecords() As Object
FromRecords = FileHelpers.CommonEngine.ReadString(FromType, MyStringBuilder.ToString)
'... maybe code here to check for certain values
Dim engine As New FileTransformEngine(Of ITransformable(Of MyDestinationClass), MyDestinationClass)
' Ideally in this next line I would like it to see certain conditions and be able to generate two records from a single source line.
Dim PayRecords() As Object = engine.TransformRecords(FromRecords)
Alternately, if there is a way to implement the "ITransformable(Of ..." TransformTo() and have it return multiple records, I could put the logic in the dynamic class definition TransformTo() method.
Thoughts?
Here is a sample of my source dynamic class:
Imports FileHelpers ' Never forget
_
Public NotInheritable Class MyDynamicClass
Implements ITransformable(Of MyDestinationClass)
_
Public Name As String
<FieldQuoted(""""c, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)> _
Public KeyType As String
Public Hours As Double
Public Function TransformTo() As MyDestinationClass Implements ITransformable(Of MyDestinationClass).TransformTo
Dim res As New MyDestinationClass
res.ContactName = Name
' Here is where I would like to say... instead of Return res
If KeyType="ABCD" Then
Dim newRes as New MyDestinationClass
newRes.Contactname = Name + " 2nd contact"
Dim resArray() as MyDestinationClass
redim resArray(1)
resArray(0) = res
resArray(1) = newRes
End If
Return resArray
' Or alternately refer to the engine, but it is not in scope for the dynamic record (is it?). Something like...
engine.AppendToDestination(new MyDestinationClass(...))
End Function
End Class
I think the main problem you're going to run into is that ITransformable.TransformTo() is spec'ed to return a single T value.
This is called in a loop when you call engine.TransformRecords(), where one output record is added for each input record.
I don't think this would be a big change if you didn't mind doing your own version of FileTransformEngine, but I don't see a clean way to do this as-is.