Can I sign .hlkx file manually using EV certificate to submit on the Microsoft website - driver

I have completed all the tests with my usb device in Hardware Lab Kit and now can prepare the .hlkx driver package to submit on the Microsoft website.
The problem is EV certificate is required for Windows 10 driver. EV certificate is provided with Safenet USB token and this USB token is located far away from computer with Hardware Lab Kit installed, so I can't sign .hlkx package in Hardware Lab Kit automatically.
The question is how can I get my Windows 10 usb drivers signed? I have the unsigned driver (sys, cab, inf ... files) and I have unsigned .hlkx driver package from Hardware Lab Kit. Can I sign my driver without submitting to the Microsoft website?

You can
Install HLK Studio to the computer where EV token is plugged in;
Copy unsigned .hlkx file to the computer with EV token;
When you will launch HLK Studio from pt1, it will promt to open .hlkx file, specify it;
On Package tab of HLK Studio do Create Package as usual.

Answer provided by Alexey didn't work for me, I eventually used the source code from this page:
https://msdn.microsoft.com/en-us/library/windows/hardware/mt674914%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
I had to do some additional tweaking:
Before using this, make sure the EV certificate is in your personal certificate store. Within the tool you have for your USB token you should be able to open the certificate and choose to “Install certificate”.
Create a new console application in visual studio and paste this source code in.
Install the nugget package “WindowsBase” to get System.IO.Packaging namespace.
With some additional source code, we can have this working:
class Program
{
static void Main(string[] args)
{
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
X509Certificate2 evCert = null;
foreach (X509Certificate2 mCert in store.Certificates)
{
if (mCert.Thumbprint == "3DF652D7EyourThumbprintF")
{
evCert = mCert;
}
}
Sign(#"C:\Path\To\Your\HLKXFile.hlkx", evCert);
}
public static void Sign(string package, X509Certificate2 certificate)
{
// Open the package to sign it
Package packageToSign = Package.Open(package);
// Specify that the digital signature should exist
// embedded in the signature part
PackageDigitalSignatureManager signatureManager = new PackageDigitalSignatureManager(packageToSign);
signatureManager.CertificateOption = CertificateEmbeddingOption.InCertificatePart;
// We want to sign every part in the package
List<Uri> partsToSign = new List<Uri>();
foreach (PackagePart part in packageToSign.GetParts())
{
partsToSign.Add(part.Uri);
}
// We will sign every relationship by type
// This will mean the signature is invalidated if *anything* is modified in //the package post-signing
List<PackageRelationshipSelector> relationshipSelectors = new List<PackageRelationshipSelector>();
foreach (PackageRelationship relationship in packageToSign.GetRelationships())
{
relationshipSelectors.Add(new PackageRelationshipSelector(relationship.SourceUri, PackageRelationshipSelectorType.Type, relationship.RelationshipType));
}
try
{
signatureManager.Sign(partsToSign, certificate, relationshipSelectors);
}
finally
{
packageToSign.Close();
}
}
}
Replace the Thumbprint with your EV certificate SHA1.

Related

Is Appium supported with MS Server 2022

We are trying to get Appium to run in a MS Server 2022 environment using the published calculator program. When it is run on a win-10 PC the application runs fine - it spawns the application and the script can control the calculator's keys.
When it is run on the 2022 server, the calculator.exe spawns but there is no control of the application keys.
There are some differences in the application/environment. In the "published" calc environment the app is addressed like this:
capabilities.setCapability("app", "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");
This was not recognized by the system so we went for the direct path:
capabilities.setCapability("app", "C:\Windows\System32\calc.exe");
Many of the other addressable features of the calculator program showed different values for their addressable IDs.
Bottom line is that there are lots of differences between the systems, has anyone gotten MSserver 2022 and Appium to work?
`public class CalculatorTest {
private static WindowsDriver<WebElement> CalculatorSession = null;
private static WebElement CalculatorResult = null;
#BeforeClass
public static void setup() {
try {
DesiredCapabilities capabilities = new DesiredCapabilities();
//capabilities.setCapability("app", "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");
capabilities.setCapability("app", "C:/Windows/System32/calc.exe");
CalculatorSession = new WindowsDriver<WebElement>(new URL("http://127.0.0.1:4727/"), capabilities);
CalculatorSession.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
CalculatorResult = CalculatorSession.findElementByAccessibilityId("CalculatorResults");
Assert.assertNotNull(CalculatorResult);
}catch(Exception e){
e.printStackTrace();
} finally {
}
}
`
org.openqa.selenium.SessionNotCreatedException: Unable to create a new remote session. Please check the server log for more details. Original error: Failed to locate opened application window with appId: C:/Windows/System32/calc.exe, and processId: 29876.
The output of the power shell:Get-StartApps command for calculator.
MSServer 2022:
Calculator         {1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\win32calc.exe
Windows 10:
Calculator Microsoft.WindowsCalculator_8wekyb3d8bbwe!App

Trying to access to a DBF file using Advantage OLE DB provider throws an exception when opening a connection

I have an ASP.NET MVC application which is trying to open below OLE DB connection:
string conString = #"Provider=Advantage OLE DB Provider;Data Source=" + dbfFilePath + ";Extended Properties=dBASE IV;";
using (dBaseConnection = new OleDbConnection(conString))
{
dBaseConnection.Open();
// Some stuff
}
I have installed below package from here.
I am using this provider in order to access a dbf file (specified on the dbfFilePath variable) and then later add some information into it. When I perform the Open command on the above code snippet I get below exception message:
Error 6420: The 'Discovery' process for the Advantage Database Server failed. Unable to connect to the Advantage Database Server. axServerConnect AdsConnect.
Previously I was using VFPOLEDB.4 provider and it was working ok when reading and modifying the dbf file. The problem is that it is only available in 32-bit (there is no version in 64-bit) and now I need it to be in 64-bit so I decided to use Advantage OLE DB provider that is available in 64-bit and as far as I know it does the same as VFPOLEDB.
What am I doing wrong?
UPDATE 2020/11/16:
If I add some parameters to connection string:
string conString = #"Provider=Advantage OLE DB Provider;Data Source=" + dbfFilePath + ";ServerType=ADS_LOCAL_SERVER;TableType=ADS_VFP;Extended Properties=dBASE IV;";
Then when opening connection I get below exception:
Error 7077: The Advantage Data Dictionary cannot be opened. axServerConnect AdsConnect
UPDATE 2020/11/20:
var dbfFilePath =#"C:\MyApp\Temp"; // using c:\MyApp\Temp\myTable.dbf does not work (below open command fails)
string conString = #"Provider=Advantage OLE DB Provider;Data Source=" + dbfFilePath + ";ServerType=ADS_LOCAL_SERVER; TableType=ADS_VFP;";
using (dBaseConnection = new OleDbConnection(conString))
{
dBaseConnection.Open();
OleDbCommand insertCommand = dBaseConnection.CreateCommand();
insertCommand.CommandText = "INSERT INTO [myTable] VALUES (2,100)";
insertCommand.ExecuteNonQuery();
}
Note: [myTable] has the same name that the dbf file within C:\MyApp\Temp.
Now open command works but when performing insertCommand.ExecuteNonQuery() it gets stuck (it does nothing).
UPDATE 2020/11/27:
Ok,I think I have detected what is happening. It works ok when using Advantage OLE DB provider in 32-bit, however, using Advantage OLE DB provider in 64-bit is not working. In both cases I use it on Windows Server 2012 R2 Standard 64-Bit and Advantage OLE DB provider is version 11.10.
I have checked this using LINQPad 5, and it works but when performing
insertCommand.ExecuteNonQuery()
... and before doing the insert to the dbf file below warning modal window appears waiting for you to click on 'Accept' button. Once you click on the button, insert is done in dbf file correctly.
So, I guess that when running my web application (ASP.NET MVC app) in production environment this warning modal windows does not appear but in fact, it is waiting for you to click on the button to proceed inserting data in the dbf file but as this warning window is not visible (it is not shown) I can click on that button and consequently ExecuteNonQuery never ends (it stalls) and it stays waiting for you to click that button indefinitely.
How can I solve this error? can I modify ads.ini in some way in order to avoid this waring message to appear so application can work?
I see you removed the VFP tag which I think most relevant to this question :)
I again tested that with these codes as a sample and it worked without a glitch:
void Main()
{
string dbfFilesPath = #"C:\PROGRAM FILES (X86)\MICROSOFT VISUAL FOXPRO 9\SAMPLES\Data";
string conString = $#"Provider=Advantage OLE DB Provider;Data Source={dbfFilesPath};ServerType=ADS_LOCAL_SERVER;TableType=ADS_VFP;";
DataTable t = new DataTable();
using (OleDbConnection cn = new OleDbConnection(conString))
using (OleDbCommand cmd = new OleDbCommand($#"insert into Customer
(cust_id, company, contact)
values
(?,?,?)", cn))
{
cmd.Parameters.Add("#cId", OleDbType.VarChar);
cmd.Parameters.Add("#company", OleDbType.VarChar);
cmd.Parameters.Add("#contact", OleDbType.VarChar);
cn.Open();
for (int i = 0; i < 10; i++)
{
cmd.Parameters["#cId"].Value = $"XYZ#{i}";
cmd.Parameters["#company"].Value = $"Company XYZ#{i}";
cmd.Parameters["#contact"].Value = $"Contact XYZ#{i}";
cmd.ExecuteNonQuery();
}
t.Load(new OleDbCommand($"select * from Customer order by cust_id desc", cn).ExecuteReader());
cn.Close();
}
t.Dump(); // tested in LinqPad AnyCPU version
}
Here is the partial result I got:
XYZ#9 Company XYZ#9 Contact XYZ#9 0.0000
XYZ#8 Company XYZ#8 Contact XYZ#8 0.0000
XYZ#7 Company XYZ#7 Contact XYZ#7 0.0000
XYZ#6 Company XYZ#6 Contact XYZ#6 0.0000
XYZ#5 Company XYZ#5 Contact XYZ#5 0.0000
XYZ#4 Company XYZ#4 Contact XYZ#4 0.0000
XYZ#3 Company XYZ#3 Contact XYZ#3 0.0000
XYZ#2 Company XYZ#2 Contact XYZ#2 0.0000
XYZ#1 Company XYZ#1 Contact XYZ#1 0.0000
XYZ#0 Company XYZ#0 Contact XYZ#0 0.0000
XXXXXX Linked Server Company 0.0000
WOLZA Wolski Zajazd Zbyszek Piestrzeniewicz Owner ul. Filtrowa 68 Warszawa 01-012 Poland (26) 642-7012 (26) 642-7012 3694
WINCA Wenna Wines Vladimir Yakovski Owner 0.0000
WILMK Wilman Kala Matti Karttunen Owner/Marketing Assistant Keskuskatu 45 Helsinki 21240 Finland 90-224 8858 90-224 8858 4400
WHITC White Clover Markets Karl Jablonski Owner 305 - 14th Ave. S., Suite 3B Seattle WA 98128 USA (206) 555-4112 (206) 555-4115 38900
WELLI Wellington Importadora Paula Parente Sales Manager Rua do Mercado, 12 Resende SP 08737-363 Brazil (14) 555-8122 3600
WARTH Wartian Herkku Pirkko Koskitalo Accounting Manager Torikatu 38 Oulu 90110 Finland 981-443655 981-443655 24200
As it is not working for you, I think it might have to do with:
Access rights. You say, you use with ASP.Net MVC, I wonder if the 'connecting account' has only read access? In IIS, basic settings as I remember there were a setting for connect as. You might at least set it to connect by an account that has full rights to that directory.
Sharing. The file might be in use shared elsewhere and for some reason your insert is waiting trying to get lock?
SET NULL ON ? Another slight possibility. You might need to execute this first, on the same connection. If there are fields that you are not supplying a value in insert but "not null" in table structure would otherwise cause it to fail.
You might start testing the same file from say within LinqPad running with administrator rights to eliminate the access rights stuff alltogether (if data directory is under program files or program files (x86), then it is a problem by itself.
I would expect an immediate error message, but who knows maybe driver is waiting for a timeout in case of write access failure?
Some ideas (the way I do it:)
Instead of trying to use VFP data with 64 bits access, you might create a server that runs in 32 bits IIS Application pool (or use its own web serving) and handles the data access via REST API (or WCF). I use 32 bits ASP.Net MVC application(s) since years with success using VFOLEDB itself.
If you think of REST API path, you might either use ASP.Net core (which is fast unlike the pre core) or use something else, say Go for building it. Go and Iris framework, for an example is an excellent fit to build a REST API server for your data over night (unlikely you would think Go, but if you do, remember to compile with x86 architecture).

Deleting .inf and .pnf Files

I manually install my driver using an .inf file. Until now, I deleted the oem.inf and .pnf files from the inf folder to uninstall. Whenever I install a new driver I delete/uninstall the old inf and pnf files.
In my old uninstalls (by deleting .inf and .pnf files), I didn't modify or delete any registry settings.
In this case do I want to change or remove any settings from the registry (for example: devnode)?
You should use the SetupUninstallOEMInf function to uninstall the .INF (and subsequently .PNF) files. This will take care of the details. pnputil (on Vista and higher) should do the equivalent thing from the command line. However, this function will not delete drivers that are currently installed (e.g. associated with a devnode).
Why are you uninstalling the old driver first? The user might already installed your driver for at least one devnode. Why not use a Microsoft-sanctioned solution such as DpInst? It will do the work required to update the driver.
Passing SUOI_FORCEDELETE to SetupUninstallOEMInf wouldn't be a good idea, cause you'd end up with lingering .INF references in your devnodes (in the registry).
At work I wrote a utility I called DriverUninstaller that deletes the devnodes and then deleted the INFs. I only use this utility for uninstallations. Upgrades are handled by DpInst, as they should be. The flow is roughly:
Enumerate them with SetupAPI (e.g. by device class if your device class is unique)
For each devnode, call SetupDiCallClassInstaller(DIF_REMOVE, ...)
Call SetupDiBuildDriverInfoList to find all .INF files for my device
For each INF, call SetupUninstallOEMInf
If there'll be interest in this utility, I might be able to persuade my employer to open-source it :-)
As the other answer points out the API to remove drivers on Windows is the SetupUninstallOEMInf method but I figured I'd add a few important notes here:
the INF path parameter must be the file name only (must not include the full path!)
this API requires Administrator privileges (this is not much of a surprise)
on 64-bit systems, the method only works when executed in the context of a 64-bit process (ie. WOW64 doesn't work)
The SetupDiGetDriverInfoDetail API can be used to query information about the relevant INF file(s) to remove for a particular device. And the SetupDiEnumDriverInfo/SetupDiBuildDriverInfoList APIs can be used to enumerate all drivers for a particular device.
// given infFilePath - full path to inf as returned by a query using SetupDiGetDriverInfoDetail
TCHAR* infFileName = GetFileNamePart(infFilePath);
if(SetupUninstallOEMInf(pInf, SUOI_FORCEDELETE, NULL))
{
// success
}else
{
DWORD errCode = GetLastError();
if(errCode == 0x02)
{
// means that the driver INF file was not found
// most likely it was already uninstalled
}else if(errCode == 0x05)
{
// must run as administrator
}else
{
// some other error code.. handle appropriately
}
}
I've open-sourced the code for a tool I wrote to perform driver uninstalls for USB and Media/Image devices. Details here: http://mdinescu.com/software-development/30-driver-hunter-programatically-uninstall-drivers-in-windows

Updating a BlackBerry application installed on a user's device

In a situation where a BlackBerry application is installed to a user's device via OTA (BIS), and that application has a "Check for updates" button, one simple approach would be to launch the browser with the address of the .jad file which would then present the user with the "You have version 1.0 installed, would you like to download and install version 1.1?" dialog. But even if there are no updates, the user would get the "You have 1.0, would you like to replace it with 1.0 dialog", which is a hassle and is meaningless.
Is there an better method for doing this in a more seamless manner? For example, are there accepted ways for the application to check the server for an update (with user's permission), inform the user if an update is available, and install the update OTA without going through the browser/jad/ota/replace/restart device loop?
Targeting RIM OS 4.1+
Thank you.
One way would be to fetch the JAD file using an HTTP connection in your app, parse for the version available on the server and only launch the browser if there is a newer version available, or after additionally asking the user if the upgrade is desired.
There are elements of the API that would allow you to also fetch the COD file(s) and install the modules yourself, but that seems like just increasing potential bug space unless you really need to avoid using the Browser OTA install.
A similar method but one that I find a bit better than Richard's thought above because the client does not need a hard-coded JAD path this way (important since JAD files may differ for different BB OS versions):
create a simple web page (php, jsp, servlet, cgi, whatever) that accepts app name and current app version as input; if you need it, also include OS version in the input.
This URL will be constructed by the client by obtaining the appropriate data (details below) and appending it to the known base URL.
the web page will parse the information, and calculate the proper version to run.
Note that you might not need all of the information above: if you only have one downloadable version of your app, you would really only need the device to send the client software version and nothing else. The calculation of proper version can be a simple hard-coded check (if ($version != LATEST_VERSION)) or something more complex, involving lookup into a database or elsewhere.
This page will output plain text, non-HTML. It will write three values, one per line:
"y" if an update is required , "n" if not.
The current-most version for this client to use. This is only necessary if you want the client to display it.
the download URL for the correct JAD.
The client application will parse that data, and if the first flag is "Y" will display message "The current version is (contents of second line). Would you like to update?" When update is selected, it will launch the URL provided in the third line.
Reference
Obtaining Application Version
import net.rim.device.api.system.ApplicationDescriptor;
...
// Returns current app version in the format Major.Minor.Minor.Build, eg 1.5.1.123
String version = ApplicationDescriptor.currentApplicationDescriptor().getVersion();
Obtaining Hardware and Platform Info
import net.rim.device.api.system.ApplicationDescriptor;
...
// Obtain the platform version string in the format A.B.C.DDD, eg 5.0.0.464
String softwareVersion = DeviceInfo.getSoftwareVersion();
// Obtain the hardware name:
String hardwareName = DeviceInfo.getDeviceName();
Launch HTTP URL
import net.rim.blackberry.api.browser.Browser;
Browser.getDefaultSession().displayPage("http://example.com");
Read HTTP file
String url = "full/url/assembled/with/data/above"
// YOU assemble "url" value - and include more error handling than is here in this sample:
HttpConnection conn;
try {
conn = ConnectionHelper.getHttpConnection(url);
LineInputStream stream = new LineInputStream(conn.openInputStream());
String lineOneYesNo = stream.readLine(true);
String lineTwoCurrentVersion = stream.readLine(true))
String lineThreeDownloadURL = stream.readLine(true))
// ***
// * Parse the data above and handle as described.
// ***
return data;
} catch (IOException e) {
// Add appropriate erorro handling here
return;
}
getHttpConnection Implementation
public static HttpConnection getHttpConnection(String URL) throws IOException {
HttpConnection c = null;
StringBuffer conn = new StringBuffer(URL);
// *** IMPORTANT ***
// YOU must define this method below, as it will append
// values to the connection string based on connection
// type (MDS, TCP, WIFI, WAP2, etc)
//
configureConnectionString(conn);
c = (HttpConnection) Connector.open(conn.toString());
int rc = c.getResponseCode();
if (rc != HttpConnection.HTTP_OK) {
throw new IOException("HTTP Error: " + rc);
}
return c;
}
Reference: Simple LineInputStream implementation
http://svn.bbssh.org/trunk/BBSSH_Common/src/org/bbssh/io/LineInputStream.java
Sample Input URL 1
This URL is constructed by the client and sent to the server:
http://example.com/versioncheck.do/app-name/hardware-name/os-version/app-version
e.g. http://example.com/versioncheck.do/MyApplication/Bold9000/5.0.466/1.5.1.0
Sample Input URL 2
Alternative format for the same thing:
http://example.com/versioncheck.php?appName=A&hardwareName=B&osVersion=C&appVersion=D
e.g. http://example.com/versioncheck.php?appName=?MyApplication&hardwareName=Bold9000?osVersion=5.0.466&appVersion=1.5.1.0
Sample Output
y
1.3.1.125
http://example.com/ota/5.0.0/MyApp.jad

Unit Test Adapter threw exception: Unable to load one or more... in ASP.NET MVC

Using ASP.NET MVC 1.0 (current) I create a new default ASP.NET MVC project using Visual Studio 2008 on an x64 machine (Server 2008) and accept all the defaults and build and run it. Apart from having to set the System.Web.* assemblies as "Copy Local" it runs and brings up the default web app. When I try and run the unit tests on this project I get:
Unit Test Adapter threw exception: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information..
Now if I repeat the same exercise using VS2008 on an x86 machine (Server 2003) then all 27 default unit tests run fine. (Also I don't need to mark the System.Web.* assemblies as "Copy Local")
Ideas for resolving the exception?
More Info:
After trying some of the solutions suggested I started commenting out parts of the boilerplate test code that is generated with a new project. As such, I believe that the error is being generated by the inclusion of one of the following classes:
public class MockIdentity : IIdentity
{
public string AuthenticationType
{
get
{
return "MockAuthentication";
}
}
public bool IsAuthenticated
{
get
{
return true;
}
}
public string Name
{
get
{
return "someUser";
}
}
}
public class MockPrincipal : IPrincipal
{
IIdentity _identity;
public IIdentity Identity
{
get
{
if (_identity == null)
{
_identity = new MockIdentity();
}
return _identity;
}
}
public bool IsInRole(string role)
{
return false;
}
}
public class MockMembershipUser : MembershipUser
{
public override bool ChangePassword(string oldPassword, string newPassword)
{
return newPassword.Equals("newPass");
}
}
public class MockHttpContext : HttpContextBase
{
private IPrincipal _user;
public override IPrincipal User
{
get
{
if (_user == null)
{
_user = new MockPrincipal();
}
return _user;
}
set
{
_user = value;
}
}
}
First, have you tried it in Release configuration? Have you done a clean on your solution?
Have you tried constructing your test project? Take out the source files for your tests, delete the project from your solution and add a new test project that references the MVC app. Then re-add the test source files.
Edit
Are the classes and interfaces that you are using and implementing in scope from your tests?
Edit
Is it referencing the x64 not the x86 dlls?
Do you have VS 2008 SP1 and .NET 3.5 SP1 installed in your Win2k8 box? By default VS 2008 installed 3.5 framework, but NOT SP1. Make sure you have both the framework SP1 and VS 2008 SP1 installed.
Did you try building your project as x86 project (Project properties ->Build ->Platform target) on x64 machine?
Also, not sure about your unit testing toolkit, but NUnit, for example, can run as tests as either x86 or x64 (on x64 machine). If one of your assemblies accesses some 32-bit code (e.g. COM object), trying to run them under x64 will result in "Unable to load one or more of the requested types" error.
I'm not sure about the subtleties of what's going on with the x64 verus x86, but using custom identity/principals can cause some interesting little glitches to happen, especially if using cassini (the built-in vshost webserver - which I think is what you end up using by running local unit tests within VS). I ran into this issue before, and rather than detailing it here, I'll post a link to some good info. Again, I'm not sure if this is related to your problem (my not being an MVC guru), but take a read through this. Food for thought:
http://www.lhotka.net/weblog/UpdateOnMyStrugglesWithTheASPNETDevelopmentServer.aspx
EDIT: so ultimately, this may be an issue of serialization failure, even if this particular edge case is not relevant. Have you tried marking your mocked iidentity/iprincpal objects as [serializable]? Remember that visual studio is a 32bit application; perhaps testing it on IIS (if not cassini) with a 64bit application pool is causing a context switch somewhere which causes the mock identities (if they get assigned as a thread's identity) to get marshalled across a boundary like that - the lack of a [serializable] attribute will probably cause a TypeLoadException.
Does it still throw if you set IIS to use a 32bit application pool (on your 64bit server)?
-Oisin
I'm wondering if the more significant difference is 2k8 vs 2k3 than 64 vs 32bit. Did you spawn Visual Studio as Administrator? An assembly might be missing due to the Virtual Store of windows 2k8. If this is a development desktop running 2k8 you might want to consider disabling the Virtual Store, it's in you policy labeled as "User Account Control: Virtualize file and registry write failures to per-user locations"
I notice some of the types (specifically IIdentity and IPrincipal) you are implementing are not located within a System.Web.* assembly.
Have you tried marking the System.Security.Principal assembly as "Copy Local"?
This reminded me of an old blog post I once read. Perhaps you can use the same technique to debug the cause of the error:
http://www.agileprogrammer.com/oneagilecoder/archive/2007/11/17.aspx

Resources