I have been looking over Microsoft's documentation and the posts here on getting search results from DirectorySearcher. I am writing code not sure the best performing way to get a lot of results from AD (right now testing with 4K results, but should scale for more).
Question 1: What is the best method?
Here are my efforts so far.
Run 1 description
I did not set the PageSize which returns 2000 (this seems to be the default on the AD server - not 1000 that I read from posts/documentation). I do not know how to get the remainder of the results. I tried making calls to Dispose() and then FindAll() multiple times. That did not work (gave me same results over and over).
Question 2: How do I get all the results this way?
Run 1:
//ds.PageSize - not setting this property
log.Debug("PageSize=" + ds.PageSize);
log.Debug("SizeLimit=" + ds.SizeLimit);
results = ds.FindAll();
log.Debug("AD count: " + results.Count);
Run 1 Log
PageSize=0
SizeLimit=0
AD Count: 2000
Run 2 description
I did the PageSize to higher than my results (though I really do not want to do this for performance fears). I got all the results as expected.
Run 2:
ds.PageSize = 5000;
log.Debug("PageSize=" + ds.PageSize);
log.Debug("SizeLimit=" + ds.SizeLimit);
results = ds.FindAll();
log.Debug("AD count: " + results.Count);
Run 2 Log
PageSize=5000
SizeLimit=0
AD Count: 4066
Run 3 description
I set the PageSize to lower than my results so not to impact performance thinking setting this to would then maybe allow the 'pagination' of results by calling Dispose() and FindAll(). Totally got unexpected results!
Run 3:
ds.PageSize = 2000;
log.Debug("PageSize=" + ds.PageSize);
log.Debug("SizeLimit=" + ds.SizeLimit);
results = ds.FindAll();
log.Debug("AD count: " + results.Count);
Run 3 Log:
PageSize=2000
SizeLimit=0
AD Count: 4066
Question 3: This makes no sense to me. Please point me to right direction. I thought subsequent calls to Dispose() and FindAll() would work here. But I got all the results on first go.
Thanks a million!
The value may have been changed in your environment - it is 1000 by default. You can set the Page Size to 1000 and the DirectorySearcher class will handle paging for you. If you set it smaller, that's fine too. You should wrap the code in a using block to make sure resources get disposed.
Related
I am enumerating installed applications using WMI, and this block is taking a relatively long time to complete no matter how I structure it. It takes 13 seconds in my environment every time. Is there a better (faster) way to check if a program is installed? (I'm using iTunes as an example program to check for)
private static string Timestamp
{
get { return DateTime.Now.ToString("HH:mm:ss.ffff"); }
}
private static void LoadInstalledPrograms()
{
List<string> installedPrograms = new List<string>();
Console.WriteLine("0 - {0}", Timestamp);
ManagementObjectSearcher mos = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
Console.WriteLine("1 - {0}", Timestamp);
ManagementObjectCollection managementObjectCollection = mos.Get();
Console.WriteLine("2 - {0}", Timestamp);
foreach (ManagementObject mo in managementObjectCollection)
{
installedPrograms.Add(mo["Name"].ToString());
}
Console.WriteLine("3 - {0}", Timestamp);
Console.WriteLine("Length - {0}", installedPrograms.Count);
}
SELECT * FROM Win32_Product
0 - 08:08:51.3762
1 - 08:08:51.3942
2 - 08:08:51.4012
3 - 08:09:04.8326
Length - 300
SELECT * FROM Win32_Product WHERE name = 'iTunes'
0 - 08:14:17.6529
1 - 08:14:17.6709
2 - 08:14:17.6779
3 - 08:14:31.0332
Length - 1
SELECT * FROM Win32_Product WHERE name LIKE 'iTunes'
0 - 08:16:38.2719
1 - 08:16:38.2899
2 - 08:16:38.2999
3 - 08:16:51.5113
Length - 1
SELECT name FROM Win32_Product WHERE name LIKE 'iTunes'
0 - 08:19:53.9144
1 - 08:19:53.9324
2 - 08:19:53.9394
3 - 08:20:07.2794
Length - 1
If you query "Win32_product" the msi-installer checks and validates every product.
The KB article http://support.microsoft.com/kb/974524 shows:
Win32_product Class is not query optimized. Queries such as “select * from Win32_Product where (name like 'Sniffer%')” require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause. This process also initiates a consistency check of packages installed, verifying and repairing the install. With an account with only user privileges, as the user account may not have access to quite a few locations, may cause delay in application launch and an event 11708 stating an installation failure.
Win32reg_AddRemovePrograms is a much lighter and effective way to do this, which avoids the calls to do a resiliency check, especially in a locked down environment. So when using Win32reg_AddRemovePrograms we will not be calling on msiprov.dll and will not be initiating a resiliency check.
So be careful with "Win32_product".
Update: nice article https://sdmsoftware.com/group-policy-blog/wmi/why-win32_product-is-bad-news/
WMI is taking it's time as you already noticed. Iterating through the registry might do the trick for you.
You might have a look at Get installed applications in a system here on stackoverflow, where both methods are mentioned.
As Bernhard points out, WMI use of Win32_Product initiates an integrity check of the package estate, and will hence be quite slow to use - and in special cases it can trigger an MSI self-repair (I have never seen this happen on my machines).
Instead of WMI, you can use the MSI automation interface directly to enumerate the applications installed via Windows Installer packages (MSI files) on the machine. This is very quick and doesn't touch WMI at all.
See this example: how to find out which products are installed - newer product are already installed MSI windows (full blown, but basic and easy to understand VBScript example - do check it out). There are many properties you can retrieve for each product, please consult the MSDN documentation for the MSI automation interface. The linked sample VBScript code and the MSDN documentation taken together should help you get going quickly I hope.
P.S: I know this is an old question, but this issue keeps coming up (specifically the slowness of WMI) - just for future reference.
As mentioned here Registry is not reliable and WMI is slow. Thus for me the best option was using Windows Installer API. Add msi.dll to your references and then adapt the following code to your needs:
public static string GetVersionOfInstalledApplication(string queryName)
{
string name;
string version;
Type type = Type.GetTypeFromProgID("WindowsInstaller.Installer");
Installer installer = Activator.CreateInstance(type) as Installer;
StringList products = installer.Products;
foreach (string productGuid in products)
{
string currName = installer.ProductInfo[productGuid, "ProductName"];
string currVersion = installer.ProductInfo[productGuid, "VersionString"];
if (currName == queryName)
{
name = currName;
version = currVersion;
return version;
}
}
return null;
}
You Should use SELECT Name FROM Win32_Product in WMI Query, it works for me
SELECT * make Load all Data Members, so using it are taking much time
Powershell 5.1 has "get-package" instead.
get-package *chrome*
Name Version Source ProviderName
---- ------- ------ ------------
Google Chrome 109.0.5414.75 msi
since some time now I try to figure out how to correctly setup this new UWF (Unified Write Filter). Unfortunately it seems there is only documentation for Win 8.1 industry (here), not for Win 10. I hope there were no relevant changes since.
I also asked this on the WindowsDevCenter but got no response so far.
Here is my problem:
With the WMI providers I got UWF enabled by now (UWF_Filter.Enable()), but I cannot protect any volume.
Also the volume list looks very strange: There are 4 entrys, everyone is with CurrentSession=True.
The first is for an volume with no drive letter, only a volume id.
The second is for C:
and then there are 2 identical for D: .
Should'nt there normally be 2 entrys per volume, one where CurrentSession is true and one where its false, meaning its the setting applied after reboot?
If I try to execute Protect on the ManagementObject with DriveLetter=C: I get an Access denied exception, I assume because its the object for the current session.
Also if I try uwfmgr.exe Volume Protect C: on the console it simply hangs: no reaction, no error, only a forever blinking cursor. EDIT: it turned out this was a problem caused by another installed software. See also below.
Do I have to enable or disable or do anything else before I can protect volumes?
Thanks in advance,
Sebastian
My system:
Windows 10 IOT Enterprise 2016 LTSB x64
1 SSD 250GB with Boot, C: and D:
Edit:
Here I asked a follow up question with some other details and a workaround. If I use uwfmgr.exe volume protect c: for example, it works and UWF_Volume now suddenly has (the correct) 2 entries for C:, one for the current and one for the next session.
However I want to avoid this, because IMHO it should be solveable by WMI only.
Edit 2: #sommmen
The partition layout is as following: One disk with 4 partitions.
Boot, 500MB
C:/ , 45GB
unknown, 500MB (Boot-Backup I think)
D:/ , ~200GB
PS:
Please could anyone create the tags uwf and uwfmgr? Would be nice :-)
Missing UWF_Volume instances often appeared after reboot in my tests. But if not, you can create them directly using ManagementClass.CreateInstance().
The problem here is that the official docs are not exactly correct. The description of the UWF_Volume.VolumeName property is:
The unique identifier of the volume on the current system. The
VolumeName is the same as the DeviceID property of the Win32_Volume
class for the volume.
from: https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/uwf-volume#properties
In fact, the DeviceID needs a slight modification, before using it as value for UWF_Volume.VolumeName:
DeviceID.Substring(4).TrimEnd('\\')
So, after removing prefix \\?\ and removing any trailing slashes you can create instances with CurrentSession=false for the specified device.
This also works in Windows 10 Pro without any uwfmgr.exe. Though, officially not recommended/supported.
Also, I was not able to delete instances, yet. So be sure to add only correct values.
Full Example:
// example value
var DeviceId_From_Win32_Volume = #"\\?\Volume{c2eac053-27e3-4f94-b28c-c2c53d5f4fe1}\";
// example value
var myDriveLetter = "C:";
var myDeviceId = DeviceId_From_Win32_Volume.Substring(4).TrimEnd('\\');
var wmiNamespace = "root\\standardcimv2\\embedded";
var className = "UWF_Volume";
var mgmtScope = new ManagementScope {Path = {NamespacePath = wmiNamespace}};
var mgmtPath = new ManagementPath(className);
var mgmtClass = new ManagementClass(mgmtScope, mgmtPath, null);
// prepare the new object
var newObj = mgmtClass.CreateInstance();
newObj.SetPropertyValue("DriveLetter", myDriveLetter);
newObj.SetPropertyValue("VolumeName", myDeviceId);
newObj.SetPropertyValue("CurrentSession", false);
newObj.SetPropertyValue("CommitPending", false);
newObj.SetPropertyValue("BindByDriveLetter", false);
// create the WMI instance
newObj.Put(new PutOptions {Type = PutType.CreateOnly});
I experience the similar issue in that I could not query the UWF_Volume with CurrentSession=False. However, there's one thing I did that seems to "generate" the UWF_Volume management object with CurrentSession=False. I ran "uwfmgr volume protect c:". Unfortunately, in your case running this causes it to hang.
Could you try running uwfmgr in cmd in admin? Also, if you run "uwfmgr get-config", would you be able to get the current setting of the write filter?
Another thing from your description: you said there are two identical volumes for D:, but if you looks closely at the properties, one would be CurrentSession=True, and the other one is CurrentSession=False. According to the documentation, if you want to make change, you must select the management object (UWF_Volume) with CurrentSession=False.
https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/uwf-volume
(scroll down to powershell script code sample section)
First of all a volume may have several partitions. They will show up as having the same drive label.
e.g.
C:/ //?/{some guid here}
C:/ //?/{some other guid here}
Now this is common for the %systemDrive% because this has the boot partition.
You can use the commands
mountvol
and
Diskpart
List volume
To figure out the right guid for your need (or you can protect both the boot partition and the system partition). Also using wmi you can look at Win32_volume under namespace cimv2 to get some more insight.
The command line util UWFmgr seems to create an UWF_VOLUME wmi instance once you run the protect command. The docs also hint that you need to create an object yourself.
function Set-ProtectVolume($driveLetter, [bool] $enabled) {
# Each volume has two entries in UWF_Volume, one for the current session and one for the next session after a restart
# You can only change the protection status of a drive for the next session
$nextConfig = Get-WMIObject -class UWF_Volume #CommonParams |
where {
$_.DriveLetter -eq "$driveLetter" -and $_.CurrentSession -eq $false
};
# If a volume entry is found for the drive letter, enable or disable protection based on the $enabled parameter
if ($nextConfig) {
Write-Host "Setting drive protection on $driveLetter to $enabled"
if ($Enabled -eq $true) {
$nextConfig.Protect() | Out-Null;
} else {
$nextConfig.Unprotect() | Out-Null;
}
}
=======> (!) im talking about this comment
# If the drive letter does not match a volume, create a new UWF_volume instance
else {
Write-Host "Error: Could not find $driveLetter. Protection is not enabled."
}
}
The docs however do not provide a method of doing this. For now it seems we need to use the command line util till someone has an example using the WMI provider.
To answer my own question: So far I have only a workaround but no real solution.
It is to check if there is an entry with CurrentSession=False and if not invoke the command directly:
ManagementObjectSearcher ms = new ManagementObjectSearcher(_Scope, new ObjectQuery("select * from UWF_Volume where VolumeName = \"" + volId + "\" AND CurrentSession=\"False\""));
ManagementObjectCollection c = ms.Get();
UInt32 res = 1;
foreach (ManagementObject mo in c)
{
// entry found: do it with WMI
res = (UInt32)mo.InvokeMethod(newState ? "Protect" : "Unprotect", new object[] { });
}
if (c.Count == 1 && res == 0)
// message: success
if (c.Count == 0)
{
// no entry found: invoke cmd
ProcessStartInfo info = new ProcessStartInfo("uwfmgr.exe", "volume " + (newState ? "Protect" : "Unprotect") + #" \\?\" + volId);
Process process = new Process();
info.Verb = "runas"; //needs admin
process.StartInfo = info;
process.Start();
process.WaitForExit();
}
This has the side effect that for a split second a command line window will pop up, but nevertheless it works well.
I'm trying to process an xml file I'm getting from a vendor. I managed to get some c# code to read in all 26 items in the xml. This code I placed into a script component in SSIS and fed that into a Union All task. I then placed a dataviewer so I can verify what I received. I use this code to add the rows to the output buffer:
Roles roles = GetWebServiceResult(wUrl);
MessageBox.Show("We have read in " + roles.Items.Length + " items");
//Add each role entry to the output buffer.
for (int i = 0; i < roles.Items.Length; i++)
{
MessageBox.Show("Adding item " + (i + 1) + " to the output");
Transfer role = getRole(roles.Items[i]);
Output0Buffer.AddRow();
Output0Buffer.roleKey = role.roleKey;
Output0Buffer.text = role.text;
Output0Buffer.Item = role.Item;
Output0Buffer.Users = role.Users;
}
When I run this I get a popup saying that there are 26 items to process, but I only get one more popup after that, telling me that item #1 has been added. the job then stops with no errors, but I only have one row of output in the dataviewer. I don't understand why this is happening when I know that there are 25 more items to add.
Additional: On a whim, I took out the Output0Buffer code and it went through all 26 items.
I figured it out. I ran it using Ctrl-F5 and studied the output in the console. Turns out a column wasn't big enough. I made that column larger and everything works. I would have thought that error would have stopped the processing.
I'm having an EWS MoveItems issue that I hope someone can help me with. With 1300 emails in the sent folder, I call the MoveItems method to move them ALL to a back-up folder and only a subset of the items get moved! Looking for a pattern, I recorded the following test numbers:
Test #1: Init Count: 1300; Actual # Moved: 722
Test #2: Init Count: 1300; Actual # Moved: 661
Test #3: Init Count: 1300; Actual # Moved: 738
With each test case my logging output shows that 1300 were found and passed to the MoveItems method, however, checking the Sent Items folder shows that not all 1300 were moved (as indicated in the above tests).
Here's a snip of my code:
...
do
{
ItemView view = new ItemView(pageSize, offset);
findResults = service.FindItems(folder, emailFilter, view);
Logger.Write("Email count on this page to be archived: " + findResults.Items.Count);
foreach (Item email in findResults)
{
itemIds.Add(email.Id);
}
offset += pageSize;
}
while (findResults.MoreAvailable);
Logger.Write("Total email Ids to be archived: " + itemIds.Count());
if (itemIds.Count() > 0)
{
Logger.Write("Archiving emails...");
service.MoveItems(itemIds, folder5.Folders[0].Id);
Logger.Write("Archive call complete.");
}
else
{
Logger.Write("No emails found to archive.");
}
...
All of this is wrapped in a try/catch block. No errors are caught.
The only other interesting item worth noting, is that the time between the "Archiving emails..." log and the "Archive call complete." is always within a second or two of being 1 minute. Possibly indicating a time-out on the call? Here's a snip of my log:
8/15/2014 4:29:43 PM - Information - Archiving emails...
8/15/2014 4:29:44 PM - Information - Creating search filters...
8/15/2014 4:29:48 PM - Information - Email count on this page to be archived: 1000
8/15/2014 4:29:49 PM - Information - Email count on this page to be archived: 300
8/15/2014 4:29:49 PM - Information - Total email Ids to be archived: 1300
8/15/2014 4:29:49 PM - Information - Archiving emails...
8/15/2014 4:30:51 PM - Information - Archive call complete.
8/15/2014 4:30:51 PM - Information - Email archival completed without errors
I'm pretty much at the end of my rope, so I appreciate any help you may be able to provide.
I had this same issue while working with EWS. I'm not sure what the "correct" solution is, but my workaround seemed to work. I profiled the move and it seemed to do fine moving a few hundred items at a time. Try moving ~250 in each call to MoveItems.
You should try processing the ServiceResponses that come back when you run the MoveItems method eg
if (itemIds.Count() > 0)
{
ServiceResponseCollection<MoveCopyItemResponse> Responses = service.MoveItems(itemIds, folder5.Id);
Int32 Success = 0;
Int32 Error = 0;
foreach (MoveCopyItemResponse respItem in Responses) {
switch (respItem.Result) {
case ServiceResult.Success: Success++;
break;
case ServiceResult.Error: Error++;
Console.WriteLine("Error with Item " + respItem.ErrorMessage);
break;
}
}
Console.WriteLine("Results Processed " + itemIds.Count + " Success " + Success + " Failed " + Error);
}
That will tell you what's going on and why some of your moves have failed. I would suspect its throttling so as Chris suggested drop your batch size down. In the past when I've written stuff to do large moves between Mailbox and Archive I went for 100 item batch size and never had a problem. When I set the batch size too large I saw time-outs and throttle errors.
Cheers
Glen
I need a way to use kby Date with bing search API in Windows Azure Marketplace to get latest news (last 24 hrs for example) in c sharp code, or any other way to control the news retrieved by news service operation to be uptodated (only latest news during the day).
Here is the Bing API v2 reference.
And here is the code samples of how to retrieve News.
Note that code samples are written on JS, but they look pretty clearly and could be easily converted to c#.
p.s. I didn't explicit piece of code doing like get the news for last 24 hrs, however there is such nice thing:
for (var i = 0; i < results.length; ++i)
{
// omitted to make answer shorted
resultStr = "<a href=\""
+ results[i].Date // <--
// omitted to make answer shorted
}
UPDATE: How to get only news for last 24 hrs
I see the solution for getting news for last 24 hr's in the following way:
Let's define stale news item as news with days out of 24 hr frame, and let 'fresh' is the opposite.
Get the top N news items(let's say initial value for N is 50).
If results do not contain stale news item - then retrieve next(*) N news items and repeat this until stale is appeared in results.
Ignore stale ones. Assing N = count of fresh news items.
Repeat steps 2-3 each next time in order to have news up to date.
Disclaimer Please note that the algorithm is very far from optimal in terms of performance, it's supposed only to demonstrate the main idea.
* How to load next N news items. Should be achiveable by loading data as pages via "$top" and "$skip" query options. In Quick Start Guide is the sample how get news('Executing a news Service Operion' section).
// This is the query expression.
string query = "Xbox Live Games";
// Create a Bing container.
string rootUrl = "https://api.datamarket.azure.com/Bing/Search";
var bingContainer = new Bing.BingSearchContainer(new Uri(rootUrl));
// The market to use.
string market = "en-us";
// Get news for science and technology.
string newsCat = "rt_ScienceAndTechnology";
// Configure bingContainer to use your credentials.
bingContainer.Credentials = new NetworkCredential(AccountKey, AccountKey);
// Build the query, limiting to 10 results.
var newsQuery =
bingContainer.News(query, null, market, null, null, null, null, newsCat, null);
newsQuery = newsQuery.AddQueryOption("$top", 10);
// Run the query and display the results.
var newsResults = newsQuery.Execute();
foreach (var result in newsResults)
{
Console.WriteLine("{0}-{1}\n\t{2}",
result.Source, result.Title, result.Description);
}
Pay attention to line newsQuery = newsQuery.AddQueryOption("$top", 10);. It should be possible(not sure if it is) to specify "$skip" options, which makes you capable of using the paging functionality.
Hope this helps.