Archive

Archive for the ‘Applications’ Category

The WMI Delphi Code Creator now can create C++ Code compatible with Borland/Embarcadero compilers

October 31, 2011 Leave a comment

After a lots of tests, I can officially announce which the WMI Delphi Code Creator now can create C++ Code compatible with the Borland/Embarcadero compilers. This new feature will be very helpful for developers using Borland/Embarcadero C++ compilers because exist very few documentation  about accessing the WMI from these compilers. The generated code can be compiled without any changes using the very old C++ Builder 5 until the modern C++ Builder XE2. The generated code uses the wbemcli.h library which is included in all the versions of  C++ builder. Another feature of this release is which the generated C++ code include all the security settings needed to establish local and remote WMI connections.

As always all your suggestions and comments are very welcome.

You can download the application from the  WMI Delphi Code Creator page.

C++ Code generated for the tool to access a WMI Class

#pragma hdrstop
#include <iostream>
using namespace std;
#include <wbemcli.h>
#include <comdef.h> 

//CREDENTIAL structure
//http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788%28v=vs.85%29.aspx
#define CRED_MAX_USERNAME_LENGTH            513
#define CRED_MAX_CREDENTIAL_BLOB_SIZE       512
#define CREDUI_MAX_USERNAME_LENGTH CRED_MAX_USERNAME_LENGTH
#define CREDUI_MAX_PASSWORD_LENGTH (CRED_MAX_CREDENTIAL_BLOB_SIZE / 2)

// The Win32_Process class represents a sequence of events on a Win32 system. Any sequence consisting of the interaction of one or more processors or interpreters, some executable code, and a set of inputs, is a descendent (or member) of this class.
// Example: A client application running on a Win32 system.

#pragma argsused
int main(int argc, char* argv[])
{
	wchar_t pszName[CREDUI_MAX_USERNAME_LENGTH+1] = L"user";
	wchar_t pszPwd[CREDUI_MAX_PASSWORD_LENGTH+1]  = L"password";
	BSTR strNetworkResource;
	//To use a WMI remote connection set localconn to false and configure the values of the pszName, pszPwd and the name of the remote machine in strNetworkResource
	bool localconn = true;	
	strNetworkResource = localconn ?  L"\\\\.\\root\\CIMV2" : L"\\\\remote--machine\\root\\CIMV2";

	COAUTHIDENTITY *userAcct =  NULL ;
	COAUTHIDENTITY authIdent;

	// Initialize COM. ------------------------------------------

	HRESULT hres;
	hres =  CoInitializeEx(0, COINIT_MULTITHREADED);
	if (FAILED(hres))
	{
        cout << "Failed to initialize COM library. Error code = 0x"	<< hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        cout << "press enter to exit" << endl;
        cin.get();		
        return 1;                  // Program has failed.
	}

	// Set general COM security levels --------------------------

	if (localconn)
		hres =  CoInitializeSecurity(
			NULL,
			-1,                          // COM authentication
			NULL,                        // Authentication services
			NULL,                        // Reserved
			RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
			RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
			NULL,                        // Authentication info
			EOAC_NONE,                   // Additional capabilities
			NULL                         // Reserved
			);
	else
		hres =  CoInitializeSecurity(
			NULL,
			-1,                          // COM authentication
			NULL,                        // Authentication services
			NULL,                        // Reserved
			RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
			RPC_C_IMP_LEVEL_IDENTIFY,    // Default Impersonation
			NULL,                        // Authentication info
			EOAC_NONE,                   // Additional capabilities
			NULL                         // Reserved
			);
			
	if (FAILED(hres))
	{
        cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        CoUninitialize();
		cout << "press enter to exit" << endl;
	    cin.get();		
        return 1;                    // Program has failed.
	}

	// Obtain the initial locator to WMI -------------------------

	IWbemLocator *pLoc = NULL;
	hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc);

	if (FAILED(hres))
	{
        cout << "Failed to create IWbemLocator object."	<< " Err code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        CoUninitialize();	    
        cout << "press enter to exit" << endl;
		cin.get();		
        return 1;                 // Program has failed.
	}

	// Connect to WMI through the IWbemLocator::ConnectServer method

	IWbemServices *pSvc = NULL;

	if (localconn)	
		hres = pLoc->ConnectServer(
			 strNetworkResource,      // Object path of WMI namespace
			 NULL,                    // User name. NULL = current user
			 NULL,                    // User password. NULL = current
			 0,                       // Locale. NULL indicates current
			 NULL,                    // Security flags.
			 0,                       // Authority (e.g. Kerberos)
			 0,                       // Context object
			 &pSvc                    // pointer to IWbemServices proxy
			 );
	else
		hres = pLoc->ConnectServer(
			strNetworkResource,  // Object path of WMI namespace
			pszName,             // User name
			pszPwd,              // User password
			NULL,                // Locale
			NULL,                // Security flags
			NULL,				 // Authority
			NULL,                // Context object
			&pSvc                // IWbemServices proxy
			);

	if (FAILED(hres))
	{
        cout << "Could not connect. Error code = 0x" << hex << hres << endl;	
        cout << _com_error(hres).ErrorMessage() << endl;
        pLoc->Release();
        CoUninitialize();
	    cout << "press enter to exit" << endl;
	    cin.get();			
        return 1;                // Program has failed.
	}

	cout << "Connected to root\\CIMV2 WMI namespace" << endl;

    // Set security levels on the proxy -------------------------
	if (localconn)
		hres = CoSetProxyBlanket(
		   pSvc,                        // Indicates the proxy to set
		   RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
		   RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
		   NULL,                        // Server principal name
		   RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
		   RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
		   NULL,                        // client identity
		   EOAC_NONE                    // proxy capabilities
		);
	else
	{
		// Create COAUTHIDENTITY that can be used for setting security on proxy
		memset(&authIdent, 0, sizeof(COAUTHIDENTITY));
		authIdent.PasswordLength = wcslen (pszPwd);
		authIdent.Password = (USHORT*)pszPwd;
		authIdent.User = (USHORT*)pszName;
		authIdent.UserLength = wcslen(pszName);
		authIdent.Domain = 0;
		authIdent.DomainLength = 0;
		authIdent.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
		userAcct = &authIdent;

		hres = CoSetProxyBlanket(
		   pSvc,                           // Indicates the proxy to set
		   RPC_C_AUTHN_DEFAULT,            // RPC_C_AUTHN_xxx
		   RPC_C_AUTHZ_DEFAULT,            // RPC_C_AUTHZ_xxx
		   COLE_DEFAULT_PRINCIPAL,         // Server principal name
		   RPC_C_AUTHN_LEVEL_PKT_PRIVACY,  // RPC_C_AUTHN_LEVEL_xxx
		   RPC_C_IMP_LEVEL_IMPERSONATE,    // RPC_C_IMP_LEVEL_xxx
		   userAcct,                       // client identity
		   EOAC_NONE                       // proxy capabilities
		);
	}

	if (FAILED(hres))
	{
        cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();		
		return 1;               // Program has failed.
	}

	// Use the IWbemServices pointer to make requests of WMI ----

	IEnumWbemClassObject* pEnumerator = NULL;
	hres = pSvc->ExecQuery(	L"WQL",	L"SELECT * FROM Win32_Process",
	WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);

	if (FAILED(hres))
	{
		cout << "ExecQuery failed" << " Error code = 0x"	<< hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();		
		return 1;               // Program has failed.
	}
    
	// Secure the enumerator proxy
	if (!localconn)
	{
		
		hres = CoSetProxyBlanket(
			pEnumerator,                    // Indicates the proxy to set
			RPC_C_AUTHN_DEFAULT,            // RPC_C_AUTHN_xxx
			RPC_C_AUTHZ_DEFAULT,            // RPC_C_AUTHZ_xxx
			COLE_DEFAULT_PRINCIPAL,         // Server principal name
			RPC_C_AUTHN_LEVEL_PKT_PRIVACY,  // RPC_C_AUTHN_LEVEL_xxx
			RPC_C_IMP_LEVEL_IMPERSONATE,    // RPC_C_IMP_LEVEL_xxx
			userAcct,                       // client identity
			EOAC_NONE                       // proxy capabilities
			);

		if (FAILED(hres))
		{
			cout << "Could not set proxy blanket on enumerator. Error code = 0x" << hex << hres << endl;
			cout << _com_error(hres).ErrorMessage() << endl;
			pEnumerator->Release();
			pSvc->Release();
			pLoc->Release();
			CoUninitialize();
			cout << "press enter to exit" << endl;
			cin.get();				
			return 1;               // Program has failed.
		}
	}

	// Get the data from the WQL sentence
	IWbemClassObject *pclsObj = NULL;
	ULONG uReturn = 0;

	while (pEnumerator)
	{
		HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

		if(0 == uReturn || FAILED(hr))
		  break;

		VARIANT vtProp;

                hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);// String
                if (!FAILED(hr))
                {
                  if ((vtProp.vt==VT_NULL) || (vtProp.vt==VT_EMPTY))
                    wcout << "Name : " << ((vtProp.vt==VT_NULL) ? "NULL" : "EMPTY") << endl;
                  else
                  if ((vtProp.vt & VT_ARRAY))
                    wcout << "Name : " << "Array types not supported (yet)" << endl;
                  else
                    wcout << "Name : " << vtProp.bstrVal << endl;
                }
                VariantClear(&vtProp);

                hr = pclsObj->Get(L"ProcessId", 0, &vtProp, 0, 0);// Uint32
                if (!FAILED(hr))
                {
                  if ((vtProp.vt==VT_NULL) || (vtProp.vt==VT_EMPTY))
                    wcout << "ProcessId : " << ((vtProp.vt==VT_NULL) ? "NULL" : "EMPTY") << endl;
                  else
                  if ((vtProp.vt & VT_ARRAY))
                    wcout << "ProcessId : " << "Array types not supported (yet)" << endl;
                  else
                    wcout << "ProcessId : " << vtProp.uintVal << endl;
                }
                VariantClear(&vtProp);

		
		pclsObj->Release();
		pclsObj=NULL;
	}

	// Cleanup

	pSvc->Release();
	pLoc->Release();
	pEnumerator->Release();
	if (pclsObj!=NULL)
	 pclsObj->Release();

	CoUninitialize();
	cout << "press enter to exit" << endl;
	cin.get();
	return 0;   // Program successfully completed.
}

C++ Code generated for the tool to execute a WMI Method

#pragma hdrstop
#include <iostream>
using namespace std;
#include <wbemcli.h>
#include <comdef.h>

//CREDENTIAL structure
//http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788%28v=vs.85%29.aspx
#define CRED_MAX_USERNAME_LENGTH            513
#define CRED_MAX_CREDENTIAL_BLOB_SIZE       512
#define CREDUI_MAX_USERNAME_LENGTH CRED_MAX_USERNAME_LENGTH
#define CREDUI_MAX_PASSWORD_LENGTH (CRED_MAX_CREDENTIAL_BLOB_SIZE / 2)

// The Create method creates a new process. 
// The method returns an integer value that can be interpretted as follows: 
// 0 - Successful completion.
// 2 - The user does not have access to the requested information.
// 3 - The user does not have sufficient privilge.
// 8 - Unknown failure.
// 9 - The path specified does not exist.
// 21 - The specified parameter is invalid.
// Other - For integer values other than those listed above, refer to Win32 error code documentation.

#pragma argsused
int main(int argc, char* argv[])
{
	wchar_t pszName[CREDUI_MAX_USERNAME_LENGTH+1] = L"user";
	wchar_t pszPwd[CREDUI_MAX_PASSWORD_LENGTH+1]  = L"password";
	BSTR strNetworkResource;
	//To use a WMI remote connection set localconn to false and configure the values of the pszName, pszPwd and the name of the remote machine in strNetworkResource
	bool localconn = true;	
	strNetworkResource = localconn ?  L"\\\\.\\root\\CIMV2" : L"\\\\remote--machine\\root\\CIMV2";

	COAUTHIDENTITY *userAcct =  NULL ;
	COAUTHIDENTITY authIdent;
	
	HRESULT hres;

	// Initialize COM. ------------------------------------------

	hres =  CoInitializeEx(0, COINIT_MULTITHREADED);
	if (FAILED(hres))
	{
		cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		cout << "press enter to exit" << endl;
		cin.get();			
		return 1;                  // Program has failed.
	}

	// Set general COM security levels --------------------------

	if (localconn)
		hres =  CoInitializeSecurity(
			NULL,
			-1,                          // COM authentication
			NULL,                        // Authentication services
			NULL,                        // Reserved
			RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
			RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
			NULL,                        // Authentication info
			EOAC_NONE,                   // Additional capabilities
			NULL                         // Reserved
			);
	else
		hres =  CoInitializeSecurity(
			NULL,
			-1,                          // COM authentication
			NULL,                        // Authentication services
			NULL,                        // Reserved
			RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
			RPC_C_IMP_LEVEL_IDENTIFY,    // Default Impersonation
			NULL,                        // Authentication info
			EOAC_NONE,                   // Additional capabilities
			NULL                         // Reserved
			);
			
                      
	if (FAILED(hres))
	{
		cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();			
		return 1;                      // Program has failed.
	}
    
    // Obtain the initial locator to WMI -------------------------

    IWbemLocator *pLoc = NULL;
    hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc);
 
	if (FAILED(hres))
	{
		cout << "Failed to create IWbemLocator object. " << "Err code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();				
		return 1;                 // Program has failed.
	}

    // Connect to WMI through the IWbemLocator::ConnectServer method

    IWbemServices *pSvc = NULL;
	
    // Connect to the root\\CIMV2 namespace
    // and obtain pointer pSvc to make IWbemServices calls.
	if (localconn)	
		hres = pLoc->ConnectServer(
			 strNetworkResource,      // Object path of WMI namespace
			 NULL,                    // User name. NULL = current user
			 NULL,                    // User password. NULL = current
			 0,                       // Locale. NULL indicates current
			 NULL,                    // Security flags.
			 0,                       // Authority (e.g. Kerberos)
			 0,                       // Context object
			 &pSvc                    // pointer to IWbemServices proxy
			 );
	else
		hres = pLoc->ConnectServer(
			strNetworkResource,  // Object path of WMI namespace
			pszName,             // User name
			pszPwd,              // User password
			NULL,                // Locale
			NULL,                // Security flags
			NULL,				 // Authority
			NULL,                // Context object
			&pSvc                // IWbemServices proxy
			);
			
			
	if (FAILED(hres))
	{
		cout << "Could not connect. Error code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		pLoc->Release();
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();					
		return 1;                // Program has failed.
	}

	cout << "Connected to root\\CIMV2 WMI namespace" << endl;

    // Set security levels on the proxy -------------------------
	if (localconn)
		hres = CoSetProxyBlanket(
		   pSvc,                        // Indicates the proxy to set
		   RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
		   RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
		   NULL,                        // Server principal name
		   RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
		   RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
		   NULL,                        // client identity
		   EOAC_NONE                    // proxy capabilities
		);
	else
	{
		// Create COAUTHIDENTITY that can be used for setting security on proxy
		memset(&authIdent, 0, sizeof(COAUTHIDENTITY));
		authIdent.PasswordLength = wcslen (pszPwd);
		authIdent.Password = (USHORT*)pszPwd;
		authIdent.User = (USHORT*)pszName;
		authIdent.UserLength = wcslen(pszName);
		authIdent.Domain = 0;
		authIdent.DomainLength = 0;
		authIdent.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
		userAcct = &authIdent;

		hres = CoSetProxyBlanket(
		   pSvc,                           // Indicates the proxy to set
		   RPC_C_AUTHN_DEFAULT,            // RPC_C_AUTHN_xxx
		   RPC_C_AUTHZ_DEFAULT,            // RPC_C_AUTHZ_xxx
		   COLE_DEFAULT_PRINCIPAL,         // Server principal name
		   RPC_C_AUTHN_LEVEL_PKT_PRIVACY,  // RPC_C_AUTHN_LEVEL_xxx
		   RPC_C_IMP_LEVEL_IMPERSONATE,    // RPC_C_IMP_LEVEL_xxx
		   userAcct,                       // client identity
		   EOAC_NONE                       // proxy capabilities
		);
	}
	
	
	if (FAILED(hres))
	{
		cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		pSvc->Release();
		pLoc->Release();     
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();					
		return 1;               // Program has failed.
	}

    // Use the IWbemServices pointer to make requests of WMI ----

    BSTR MethodName = SysAllocString(L"Create");
    BSTR ClassName = SysAllocString(L"Win32_Process");

    IWbemClassObject* pClass = NULL;
    hres = pSvc->GetObject(ClassName, 0, NULL, &pClass, NULL);

    IWbemClassObject* pInParamsDefinition = NULL;
    hres = pClass->GetMethod(MethodName, 0, &pInParamsDefinition, NULL);

    IWbemClassObject* pClassInstance = NULL;
    hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);

    VARIANT varCommand;
    varCommand.vt = VT_BSTR;
    varCommand.bstrVal = L"cmd.exe";
    hres = pClassInstance->Put(L"CommandLine", 0, &varCommand, 0);
    VariantClear(&varCommand);
	

	// Execute Method
	IWbemClassObject* pOutParams = NULL;
	hres = pSvc->ExecMethod(ClassName, MethodName, 0,
	NULL, pClassInstance, &pOutParams, NULL);

	if (FAILED(hres))
	{
		cout << "Could not execute method. Error code = 0x" << hex << hres << endl;
		cout << _com_error(hres).ErrorMessage() << endl;
		SysFreeString(ClassName);
		SysFreeString(MethodName);
		if (pClass)			
		pClass->Release();
		if (pInParamsDefinition)			
		pInParamsDefinition->Release();
		if (pOutParams)			
		pOutParams->Release();
		if (pSvc)			
		pSvc->Release();
		if (pLoc)			
		pLoc->Release();     
		CoUninitialize();
		cout << "press enter to exit" << endl;
		cin.get();			
		return 1;               // Program has failed.
	}


	
    VARIANT varReturnValue;
    hres = pOutParams->Get(L"ProcessId", 0, &varReturnValue, NULL, 0);
    if (!FAILED(hres))
    {
      if ((varReturnValue.vt==VT_NULL) || (varReturnValue.vt==VT_EMPTY))
        wcout << "ProcessId : " << ((varReturnValue.vt==VT_NULL) ? "NULL" : "EMPTY") << endl;
      else
      if ((varReturnValue.vt & VT_ARRAY))
        wcout << "ProcessId : " << "Array types not supported (yet)" << endl;
      else
        wcout << "ProcessId : " << varReturnValue.uintVal << endl;
    }
    VariantClear(&varReturnValue);

    hres = pOutParams->Get(L"ReturnValue", 0, &varReturnValue, NULL, 0);
    if (!FAILED(hres))
    {
      if ((varReturnValue.vt==VT_NULL) || (varReturnValue.vt==VT_EMPTY))
        wcout << "ReturnValue : " << ((varReturnValue.vt==VT_NULL) ? "NULL" : "EMPTY") << endl;
      else
      if ((varReturnValue.vt & VT_ARRAY))
        wcout << "ReturnValue : " << "Array types not supported (yet)" << endl;
      else
        wcout << "ReturnValue : " << varReturnValue.uintVal << endl;
    }
    VariantClear(&varReturnValue);

	

    // Clean up    
    SysFreeString(ClassName);
    SysFreeString(MethodName);
    pClass->Release();
    pInParamsDefinition->Release();
    pOutParams->Release();
    pLoc->Release();
    pSvc->Release();
    CoUninitialize();
    cout << "press enter to exit" << endl;
    cin.get();	
    return 0;
}

Categories: Applications, C++ Builder, Cpp, WMI

Delphi IDE Theme Editor – Now supports Lazarus

July 4, 2011 1 comment

The Delphi IDE Theme Editor now supports the Lazarus IDE , so you can use any of the themes included in the installer or make your own theme. you can add the themes to the Lazarus IDE coping the generated themes (check  the Themes Lazarus folder in the installation path or download  the included Lazarus themes from here) to the primary_conf_path/userschemes/   folder (example in windows vista the folder is C:\Users\<Windows User>\AppData\Local\lazarus\userschemes) or let to the tool apply the current theme to Lazarus IDE.

For more information about the Lazarus color schemes check these articles

ScreenShots

Categories: Applications, Lazarus

Delphi Preview Handler – source code published in google code

June 26, 2011 1 comment

A few hours ago I  published in the google code site the code of the Delphi Preview Handler project, under the Mozilla Public License 1.1 , now you can browse in the code and make your suggestions and report bugs using the issue page of the project. I also want to thank to Uwe Raabe by allow me use part of his work in this article to rewrite some parts of the preview handler.

Categories: Applications, Delphi

WMI Delphi Code Creator – New features and source code published in google code

June 15, 2011 Leave a comment

In the last months I’ been working in a new version of the WMI Delphi Code Creator, porting the original code from Delphi 2007 to XE, adding new features like support for Delphi Prism and Free Pascal, and improving the source code generated by the tool. Now is time to show the results, so the new version is here with many new features,  also  the full source code is now available in the google code project hosting site under the Mozilla Public License 1.1

Check the list of features of the current version

  • Can generate object pascal code compatible with one of these compilers Delphi Win32, Delphi -Prism (Oxygene) , Free Pascal
  • The Delphi code generated is compatible with  Delphi 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE.
  • The Free Pascal code generated is compatible with these versions 2.4.2, 2.4.4
  • The Delphi prism .Net  (Oxygene) generated code is compatible with all the versions up to 4.0.23.741 (in newer versions must work too)
  • Full access to metadata of any WMI Class registered in the system including qualifiers, mof definition, properties, methods, events
  • You can access directly from the application the MSDN web page related to the WMI Class which your are using.
  • Compile and run the generated code directly form the application using the selected compiler.
  • Open the the generated Delphi code in any of these  Delphi IDE’s 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE
  • Open the the generated Free Pascal code directly in the Lazarus IDE.
  • Open the the generated Delphi Prism code directly in Visual Studio 2008, Visual Studio 2010 or MonoDevelop.
  • Runs on Windows XP, 2003, 2008, Vista and 7.
  • Themes support for Syntax highlighting (+50 themes included) compatible with the Delphi IDE Theme Editor.

For download the new version of the application and more details go to project page

Introducing the Delphi Preview Handler

June 2, 2011 61 comments

To download the last version of the delphi preview handler check the new page of the project.

 
The current beta version of the preview handler (DelphiPreviewHandler.dll)  is 1.0.1.168  please check the version installed before you report bugs.

Some weeks ago I began a parallel project to the Delphi IDE Theme Editor, I name this project Delphi Preview Handler, which basically is a preview handler for windows vista and  7 which allow you read your object pascal, C++ and Assembly code with Syntax highlighting without open in a editor, this preview handler can render these file extensions .pp, .lpr, .lfm, .lpk, .inc, .pas, .dpr,.dfm, .dpk,.dproj, .bdsproj,.c, .cpp,. cc,.h, .hpp,.hh, .cxx, .hxx, . cu, .asm.

Features

  • Supports Windows Vista y Windows 7 32 and 64 bits.
  • +50 themes included to see your code with style
  • Themes compatibles with the Delphi IDE Theme Editor
  • Support these file extensions .pp, .lpr, .lfm, .lpk, .inc, .pas, .dpr,.dfm, .dpk,.dproj, .bdsproj,.c, .cpp,. cc,.h, .hpp,.hh, .cxx, .hxx, . cu, .asm.

Technical Stuff

  • Written in Delphi XE
  • Components used SynEdit

Screenshots

Video

Source Code

Soon, very soon.

Manual Installation

Follow these steps to register the preview handler

1. Choose Start > All Programs > Accessories.
2. Right-click on Command Prompt, Select Run As Administrator, and Authenticate.
3. Go to Delphi Preview Hander folder in Command Prompt.
4. Run “Register.bat”
5. Enjoy

Important Note about editing the Settings.ini file

You can change  the theme used in the Preview Handler editing the Settings.ini file, but before to do this you must unregister the preview handler and close all the explorer windows. to avoid problems. please follow these steps :

1. unregister the dll

2.close all the windows explorer

3.edit the Settings.ini file

4.save the changes

5.register the dll

6.enjoy

Important Note about installing  a new version

In order to avoid problems you must follow these steps when you install or register a new version of the preview handler.

1. close all the windows explorer windows which have the preview handler active or the preview handler was used (remember the dll remains in memory until the windows explorer was closed)

2. unregister the previous version executing the uninstaller located in C:\Program Files (x86)\TheRoadToDelphi\DelphiPreviewHandler or C:\Program Files\TheRoadToDelphi\DelphiPreviewHandler

3. If you install the preview handler manually you must unregister using the UnRegister.bat  running as admin.

4.Now proceed with the installation of the new version.

Download the Binaries of the Delphi Preview Handler from here

Download the Installer (recommended) of the Delphi Preview Handler from here

All your suggestions and comments are very welcome.

Delphi IDE Theme Editor – New features added

May 22, 2011 5 comments

New features was added to the Delphi IDE Theme Editor.

Added Color dialog with color picker.

Added option to report bugs and suggestions directly

The themes included with the tool were improved.

Some Minor bugs fixed

Check the screenshots

To download the last version go to the main page of the project.

Remember. to make your suggestions or report bugs use the Issue page of the project.

Categories: Applications, Delphi

Added support for Delphi 5 and 6 in the Delphi IDE Theme Editor

April 26, 2011 Leave a comment

Some fellows coders ask me (and request) about add support to the delphi 5 and delphi 6 IDEs. these versions was not originally supported by the  Delphi IDE Theme Editor because these IDEs gives you a 16 fixed colors palette. So this causes  limitations to manage the themes.

Check this image for the IDE editor options in Delphi 5 which shows the 16 colors available

the option to assign any color to the Highlight elements was added in the Delphi 7 version

The first thing I thought to manage the 16 palette colors was use specific themes with contains combinations of colors using these 16 colors. But quickly discarded this solution because would be necessary handle specific themes for specific versions of the delphi IDEs.  So finally I decide convert the colors themes to the 16 color palette using some algorithm to find the closest color to the palette. the algorithm chosen was the Euclidean distance where  closest color is determined by the distance between the two corresponding points in three-dimensional space.  for example to find the distance of two colors  (r1,g1,b1) and (r2,g2,b2)  the formula look like this

Now the dephi implementation of the algorithm to find the “straight-line distance” or “nearest color” using the Euclidean distance:


const
  DelphiOldColorsCount =16;
  //this is the 16 colors palette used by delphi 5 and delphi 6 IDEs (BGR format)
  DelphiOldColorsList: array[0..DelphiOldColorsCount-1] of TColor =
  (
    $000000,$000080,$008000,$008080,
    $800000,$800080,$808000,$C0C0C0,
    $808080,$0000FF,$00FF00,$00FFFF,
    $FF0000,$FF00FF,$FFFF00,$FFFFFF
  )

function GetIndexClosestColor(AColor:TColor) : Integer;
var
  SqrDist,SmallSqrDist  : Double;
  i,R1,G1,B1,R2,G2,B2   : Integer;
begin
  Result:=0;
  //set the max distance possible
  SmallSqrDist := Sqrt(SQR(255)*3);
  //get the RGB components of the original color
  R1 := GetRValue(AColor);
  G1 := GetGValue(AColor);
  B1 := GetBValue(AColor);

    for i := 0 to DelphiOldColorsCount-1 do
    begin
      //get the RGB components of the palette color
      R2 := GetRValue(DelphiOldColorsList[i]);
      G2 := GetGValue(DelphiOldColorsList[i]);
      B2 := GetBValue(DelphiOldColorsList[i]);
      //calculate the euclidean distance
      SqrDist := Sqrt(SQR(R1 - R2) + SQR(G1 - G2) + SQR(B1 - B2));
      if SqrDist < SmallSqrDist then
      begin
       Result := i;
       SmallSqrDist := SqrDist;
      end
    end
end;

Applying the above function the results are

Aqua Theme Original (Delphi 7 and Above)

Aqua Theme Modified (Delphi 5 and Delphi 6)


NightFall Theme Original (Delphi 7 and Above)

NightFall Theme Modified (Delphi 5 and Delphi 6)

I think which the final result is acceptable (remember the palette is 16 colors only) . Now you can download the updated version of the Delphi IDE Theme Editor from here.

Categories: Applications, Delphi

Delphi IDE Theme Editor – New features

March 20, 2011 11 comments

New features was added to the Delphi IDE Theme Editor

  • The GUI was improved to reflect more elements of the syntax highlighting (Active Line, Enabled break point, Disabled break point, execution point, error line), also when you click in any place in the editor the associated element is shown in the selection list.

  • New option to change the Hue/Saturation of any theme. This functionality allow you create new themes in seconds

 

  • More Themes added, now you have 50+ themes to personalize you Delphi IDE.
  • Finally an new page was created on my blog to publish the last news and features added to the Delphi IDE Theme Editor.

Download the Delphi IDE Theme Editor from here

And remember your suggestions and comments are very important to improve the application.

Categories: Applications, Delphi

Is Your Delphi IDE Hot or Not? – Introducing the Delphi IDE Theme Editor

March 14, 2011 54 comments

UPDATE : Visit the new page of the project to check the new features.

The last weekend I was working in a new project called Delphi IDE Theme Editor. this tool allow to change the Delphi (Rad studio) color settings.
the application was written using Delphi XE and the Unicode SynEdit components.

Here some features

  • Supports Delphi 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE
  • Can import Visual Studio Themes 2003,2008,2010 (.vssettings)
  • You can revert any changes made to the IDE pressing the button “Set default theme values for selected IDE”
  • 35 themes are included, ready to use in your Delphi IDE.

Screenshot of the application

Look the dephi IDE

check this video to see how the application set a new theme to Delphi IDE

See how the tool can import a Visual Studio Theme (.vssettings) and apply this style to Delphi IDE.

some tips

  • Check the site studiostyles to get a lot of themes which you can import to the Delphi IDE.
  • If your system does not have the Consolas font installed you can download the Consolas Font Pack for Microsoft Visual Studio 2005 or 2008 from here

In the next days I will publish the full the source code and the technical details of the tool, so stay tuned.

Let me know If you have any suggestion or comments to improve the application.

Download the application from here

Categories: Applications, Delphi

A New project – Delphi (Object Pascal) WMI class code generator

December 26, 2010 14 comments

The lasts weeks I’ve been working in a new project, called Delphi WMI class code generator. let me tell you about it.

The WMI (Windows Management Instrumentation) is formed by many classes, this classes exposes properties and methods. Also each class, property and method have qualifiers which are something like attributes, these qualifiers include descriptions about the classes, method, parameters or properties, types and many more useful information.

Now to access the properties of a wmi class from object pascal code is a very easy task, as was shown in this post, but by the other side to access the methods is little more complicated, because you need to known if the method is static or dynamic. also you must deal in some cases with complicated parameters which must be variants arrays, objects or datetime (in UTC format). and finally some of these parameters can be optional. so if you are only an occasional user of the WMI you must figure out a lot of thinks before to use it.

Because that and to the experience gained when I wrote the WMI Delphi Code Creator application, I decided to go a couple of steps forward and create tool which facilitate the access to the properties and methods exposed by the WMI classes from Object Pascal code.

The result was a code generator which parse the very rich meta-data of the wmi classes and extract the properties and methods and convert into a Object pascal class.

Now Let me show a sample code generated by the tool for the Win32_Share Wmi class.

/// <summary>
/// Unit generated using the Delphi Wmi class generator tool, Copyright Rodrigo Ruz V. 2010
/// Application version 0.1.0.120
/// WMI version 7600.16385
/// Creation Date 24-12-2010 09:38:11
/// Namespace root\CIMV2 Class Win32_Share
/// MSDN info about this class http://msdn2.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/Win32_Share.asp
/// </summary>

{$IFDEF FPC}
 {$MODE DELPHI} {$H+}
 {$DEFINE OLD_DELPHI}
{$ENDIF}

unit uWin32_Share;

interface

uses
 Classes,
 Activex,
 Variants,
 ComObj,
 uWmiDelphiClass;

type
{$IFDEF FPC}
 Cardinal=Longint;
 Int64=Integer;
 Word=Longint;
{$ENDIF}
{$IFNDEF FPC}
 {$IF CompilerVersion <= 15}
 {$DEFINE OLD_DELPHI}
 {$IFEND}
{$ENDIF}
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Win32_Share class represents a shared resource on a Win32 system. This may be a disk drive, printer, interprocess communication, or other shareable device.
 /// Example: C:\PUBLIC.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 TWin32_Share=class(TWmiClass)
 private
 FAccessMask                         : Cardinal;
 FAllowMaximum                       : Boolean;
 FCaption                            : String;
 FDescription                        : String;
 FInstallDate                        : TDateTime;
 FMaximumAllowed                     : Cardinal;
 FName                               : String;
 FPath                               : String;
 FStatus                             : String;
 FType                               : Cardinal;
 public
 constructor Create(LoadWmiData : boolean=True); overload;
 destructor Destroy;Override;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// This property has been deprecated in favour of the GetAccessMask method of this
 /// class due to the expense of calling GetEffectiveRightsFromAcl. The value will
 /// be set to NULL
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property AccessMask : Cardinal read FAccessMask;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The AllowMaximum property indicates whether the number of concurrent users for this resource has been limited.
 /// Values: TRUE or FALSE. A value of TRUE indicates the number of concurrent users of this resource has not been limited and the value in the MaximumAllowed property is ignored.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property AllowMaximum : Boolean read FAllowMaximum;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Caption property is a short textual description (one-line string) of the
 /// object.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Caption : String read FCaption;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Description property provides a textual description of the object.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Description : String read FDescription;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The InstallDate property is datetime value indicating when the object was
 /// installed. A lack of a value does not indicate that the object is not installed.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property InstallDate : TDateTime read FInstallDate;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The MaximumAllowed property indicates the limit on the maximum number of users allowed to use this resource concurrently. The value is only valid if the AllowMaximum member set to FALSE
 /// Example: 10.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property MaximumAllowed : Cardinal read FMaximumAllowed;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Name property indicates the alias given to a path set up as a share on a  Win32 system.
 /// Example: public.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Name : String read FName;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Path property indicates the local path of the Win32 share.
 /// Example: C:\Program Files
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Path : String read FPath;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Status property is a string indicating the current status of the object.
 /// Various operational and non-operational statuses can be defined. Operational
 /// statuses are "OK", "Degraded" and "Pred Fail". "Pred Fail" indicates that an
 /// element may be functioning properly but predicting a failure in the near
 /// future. An example is a SMART-enabled hard drive. Non-operational statuses can
 /// also be specified. These are "Error", "Starting", "Stopping" and "Service". The
 /// latter, "Service", could apply during mirror-resilvering of a disk, reload of a
 /// user permissions list, or other administrative work. Not all such work is on-
 /// line, yet the managed element is neither "OK" nor in one of the other states.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Status : String read FStatus;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Type property specifies the type of resource being shared. Types include
 /// disk drives, print queues, interprocess communications (IPC), and general
 /// devices.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF} : Cardinal read FType;
 function Create(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal;const Name : String;const Password : String;const Path : String;const {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF} : Cardinal): Integer;overload;
 function SetShareInfo(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal): Integer;
 function GetAccessMask: Integer;
 function Delete: Integer;
 procedure SetCollectionIndex(Index : Integer); override;
 end;

 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// Return the description for the value of the property TWin32_Share.Type
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 function GetTypeAsString(const APropValue:Cardinal) : string;

implementation

function GetTypeAsString(const APropValue:Cardinal) : string;
begin
Result:='';
 case APropValue of
 0 : Result:='Disk Drive';
 1 : Result:='Print Queue';
 2 : Result:='Device';
 3 : Result:='IPC';
 2147483648 : Result:='Disk Drive Admin';
 2147483649 : Result:='Print Queue Admin';
 2147483650 : Result:='Device Admin';
 2147483651 : Result:='IPC Admin';
 end;
end;

{TWin32_Share}

constructor TWin32_Share.Create(LoadWmiData : boolean=True);
begin
 inherited Create(LoadWmiData,'root\CIMV2','Win32_Share');
end;

destructor TWin32_Share.Destroy;
begin
 inherited;
end;

procedure TWin32_Share.SetCollectionIndex(Index : Integer);
begin
 if (Index>=0) and (Index<=FWmiCollection.Count-1) and (FWmiCollectionIndex<>Index) then
 begin
 FWmiCollectionIndex:=Index;
 FAccessMask          := VarCardinalNull(inherited Value['AccessMask']);
 FAllowMaximum        := VarBoolNull(inherited Value['AllowMaximum']);
 FCaption             := VarStrNull(inherited Value['Caption']);
 FDescription         := VarStrNull(inherited Value['Description']);
 FInstallDate         := VarDateTimeNull(inherited Value['InstallDate']);
 FMaximumAllowed      := VarCardinalNull(inherited Value['MaximumAllowed']);
 FName                := VarStrNull(inherited Value['Name']);
 FPath                := VarStrNull(inherited Value['Path']);
 FStatus              := VarStrNull(inherited Value['Status']);
 FType                := VarCardinalNull(inherited Value['Type']);
 end;
end;

//static, OutParams=1, InParams>0
function TWin32_Share.Create(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal;const Name : String;const Password : String;const Path : String;const {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF} : Cardinal): Integer;
var
 objInParams                : OleVariant;
 objOutParams               : OleVariant;
begin
 objInParams                 := GetInstanceOf.Methods_.Item('Create').InParameters.SpawnInstance_();
 objInParams.Properties_.Item('Access').Value  := Access;
 objInParams.Properties_.Item('Description').Value  := Description;
 objInParams.Properties_.Item('MaximumAllowed').Value  := MaximumAllowed;
 objInParams.Properties_.Item('Name').Value  := Name;
 objInParams.Properties_.Item('Password').Value  := Password;
 objInParams.Properties_.Item('Path').Value  := Path;
 objInParams.Properties_.Item('Type').Value  := {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF};
 objOutParams                := WMIService.ExecMethod(WmiClass, 'Create', objInParams, 0, GetNullValue);
 Result := VarIntegerNull(objOutParams.ReturnValue);
end;

//not static, OutParams=1, InParams>0
function TWin32_Share.SetShareInfo(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal): Integer;
var
 ReturnValue : OleVariant;
begin
 ReturnValue := GetInstanceOf.SetShareInfo(Access,Description,MaximumAllowed);
 Result      := VarIntegerNull(ReturnValue);
end;

//not static, OutParams=1, InParams=0
function TWin32_Share.GetAccessMask: integer;
var
 ReturnValue : OleVariant;
begin
 ReturnValue := GetInstanceOf.GetAccessMask;
 Result      := VarIntegerNull(ReturnValue);
end;

//not static, OutParams=1, InParams=0
function TWin32_Share.Delete: integer;
var
 ReturnValue : OleVariant;
begin
 ReturnValue := GetInstanceOf.Delete;
 Result      := VarIntegerNull(ReturnValue);
end;
end.

as you can see the generated code is a full documented class compatible with the delphi help insight feature, available since Delphi 2005.

check this screen-shot which show the help insight for the Getowner method of the Win32_Process class.

This tool not only facilitate the access to the wmi, also give you information about every single WMI class, method and property.

here some features of the application

  • The code generated is compatible Delphi 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE and the Free Pascal Compiler 2.2.4 (win32)
  • Create full documented classes compatible with the help insight feature, available since Delphi 2005.
    Note : the language of the description of the methods, parameters and properties depends on of the language of the windows where you generate the units.
  • Create additional helper functions to retrieve the description of the returned values for the properties and functions.
  • Support access to the WMI of the remote computers.

Now see this sample application which uses a class generated by the tool to access the BIOS information of a Remote PC.

program TestRemote;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  uWmiDelphiClass in '..\..\uWmiDelphiClass.pas', //the base class must be always included
  uWin32_BIOS in '..\..\root_CIMV2\uWin32_BIOS.pas'; //the class with the BIOs information

var
  RemoteBiosInfo : TWin32_BIOS;
  i              : integer;
begin
   try
     RemoteBiosInfo:=TWin32_BIOS.Create(False); //Create a instance of the TWin32_BIOS, the false value indicate which not load the Values when calls the constructor.
     try

       RemoteBiosInfo.WmiServer:='192.168.217.128'; //the remote pc name or IP
       RemoteBiosInfo.WmiUser  :='Administrator'; //the user used to establish the connection
       RemoteBiosInfo.WmiPass  :='password'; //the password
       RemoteBiosInfo.LoadWmiData; //now load the the data of the class

       if RemoteBiosInfo.WmiConnected then  //check if the connection was established
       begin
         Writeln('Serial Number       '+RemoteBiosInfo.SerialNumber);
         Writeln('BuildNumber         '+RemoteBiosInfo.BuildNumber);
         if RemoteBiosInfo.BIOSVersion.Count>0 then
         Writeln('Version             '+RemoteBiosInfo.BIOSVersion[0]);
         Writeln('Identification Code '+RemoteBiosInfo.IdentificationCode);
         Writeln('Manufacturer        '+RemoteBiosInfo.Manufacturer);
         Writeln('SoftwareElementID   '+RemoteBiosInfo.SoftwareElementID);
         Writeln('Release Date        '+DateToStr(RemoteBiosInfo.ReleaseDate));
         Writeln('Install Date        '+DateToStr(RemoteBiosInfo.InstallDate));
         Writeln('Target S.O          '+GetTargetOperatingSystemAsString(RemoteBiosInfo.TargetOperatingSystem));
         Writeln('Soft. element state '+GetSoftwareElementStateAsString(RemoteBiosInfo.SoftwareElementState));

         Writeln('');
         Writeln('Bios Characteristics');
         Writeln('--------------------');
         for i:=Low(RemoteBiosInfo.BiosCharacteristics)  to High(RemoteBiosInfo.BiosCharacteristics) do
          Writeln(GetBiosCharacteristicsAsString(RemoteBiosInfo.BiosCharacteristics[i]));
       end
       else
       Writeln('No connected');
     finally
      RemoteBiosInfo.Free;
     end;
   except
    on E:Exception do
     Writeln(E.Classname, ': ', E.Message);
   end;

 Readln;
end.

You can found more information about the internals, the full source code, demos and samples of this tool in the google code project page.

See you, and happy new year.

Categories: Applications, Delphi, Lazarus, WMI
Follow

Get every new post delivered to your Inbox.

Join 61 other followers