Canonical Voices

Posts tagged with 'ctypes'

mandel

The new Ubuntu One Windows client is very close to be released (we have already been sending the new code to our beta testers) and in order to make life easier to new user we wanted to provide a migration script that will allow the user migrate his data to the new client and uninstall the old one. In order to be able to know if the old msi is present in the system we had to use the Windows Installer SDK to query the installed applications and find if the old Ubuntu One client is present.

The following code is a small script that contains the functions to query the installed software in the system which is very similar to the script found in WiLstPrd.vbs but using python instead of VB and ctypes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# This scripts allows to get a list of all installed products in a windows
# machine. The code uses ctypes becuase there were a number of issues when
# trying to achieve the same win win32com.client
from collections import namedtuple
from ctypes import byref, create_unicode_buffer, windll
from ctypes.wintypes import DWORD
from itertools import count
 
# defined at http://msdn.microsoft.com/en-us/library/aa370101(v=VS.85).aspx
UID_BUFFER_SIZE = 39
PROPERTY_BUFFER_SIZE = 256 
ERROR_MORE_DATA = 234
ERROR_INVALID_PARAMETER = 87
ERROR_SUCCESS = 0
ERROR_NO_MORE_ITEMS = 259 
ERROR_UNKNOWN_PRODUCT = 1605 
 
# diff propoerties of a product, not all products have all properties
PRODUCT_PROPERTIES = [u'Language',
                      u'ProductName',
                      u'PackageCode',
                      u'Transforms',
                      u'AssignmentType',
                      u'PackageName',
                      u'InstalledProductName',
                      u'VersionString',
                      u'RegCompany',
                      u'RegOwner',
                      u'ProductID',
                      u'ProductIcon',
                      u'InstallLocation',
                      u'InstallSource',
                      u'InstallDate',
                      u'Publisher',
                      u'LocalPackage',
                      u'HelpLink',
                      u'HelpTelephone',
                      u'URLInfoAbout',
                      u'URLUpdateInfo',] 
 
# class to be used for python users :)
Product = namedtuple('Product', PRODUCT_PROPERTIES)
 
 
def get_property_for_product(product, property, buf_size=PROPERTY_BUFFER_SIZE):
    """Retruns the value of a fiven property from a product."""
    property_buffer = create_unicode_buffer(buf_size)
    size = DWORD(buf_size)
    result = windll.msi.MsiGetProductInfoW(product, property, property_buffer,
                                           byref(size))
    if result == ERROR_MORE_DATA:
        return get_property_for_product(product, property,
                2 * buf_size)
    elif result == ERROR_SUCCESS:
        return property_buffer.value
    else:
        return None
 
 
def populate_product(uid):
    """Return a Product with the different present data."""
    properties = []
    for property in PRODUCT_PROPERTIES:
        properties.append(get_property_for_product(uid, property))
    return Product(*properties) 
 
 
def get_installed_products_uids():
    """Returns a list with all the different uid of the installed apps."""
    # enum will return an error code according to the result of the app
    products = []
    for i in count(0):
        uid_buffer = create_unicode_buffer(UID_BUFFER_SIZE)
        result = windll.msi.MsiEnumProductsW(i, uid_buffer)
        if result == ERROR_NO_MORE_ITEMS:
            # done interating over the collection
            break
        products.append(uid_buffer.value)
    return products
 
 
def get_installed_products():
    """Returns a collection of products that are installed in the system."""
    products = []
    for puid in  get_installed_products_uids():
        products.append(populate_product(puid))
    return products 
 
 
def is_product_installed_uid(uid):
    """Return if a product with the given id is installed.
 
    uid Most be a unicode object with the uid of the product using
    the following format {uid}
    """
    # we try to get the VersisonString for the uid, if we get an error it means
    # that the product is not installed in the system.
    buf_size = 256
    uid_buffer = create_unicode_buffer(uid)
    property = u'VersionString'
    property_buffer = create_unicode_buffer(buf_size)
    size = DWORD(buf_size)
    result = windll.msi.MsiGetProductInfoW(uid_buffer, property, property_buffer,
                                           byref(size))
    if result == ERROR_UNKNOWN_PRODUCT:
        return False
    else:
        return True

The above code will allow a python developer to check which products are installed on Windows as well as to know if a product with the given UID is indeed installed.

Read more
mandel

Sometimes the Moirae (lovely three women, aren’t they?) decide that your project is going to have a complicated live, and this is what I have been facing so far with the port of Ubuntu One to Windows. This means that things that I do not anticipate to go wrong will go wrong. As an example of this is what has currently broken the nightlies of Ubuntu One on any platform (at least we have the same features in all platforms now ;) ). The issue has happened due to some changed that added in Ubuntu SSO Client that would allow to use pykeyring on windows and the COM to detect network changes.

In Ubuntu SSO Client there was an error in the setup.py that would have the following trace

ERROR: Python module pythoncom not found
Traceback (most recent call last):
  File "setup.py", line 105, in <module>
    'clean' : SSOClean})
  File "/usr/lib/python2.6/dist-packages/DistUtilsExtra/auto.py", line 95, in setup
    __requires(attrs, src_all)
  File "/usr/lib/python2.6/dist-packages/DistUtilsExtra/auto.py", line 392, in __requires
    __add_imports(imports, s, attrs)
  File "/usr/lib/python2.6/dist-packages/DistUtilsExtra/auto.py", line 341, in __add_imports
    if __external_mod(node.module, attrs):
  File "/usr/lib/python2.6/dist-packages/DistUtilsExtra/auto.py", line 317, in __external_mod
    path = __import__(module).__file__
  File "/usr/lib/python2.6/ctypes/wintypes.py", line 23, in <module>
    class VARIANT_BOOL(_SimpleCData):
ValueError: _type_ 'v' not supported

Well that is little odd, isn’t it? Why would Distutils-extra have an issue with wintypes, shouldn’t it just return an error to the stderr and leave it like that?. Well interestingly enough, the following returns a ValueError on Linux:

import ctypes.wintypes

He, interesting (I can assure you I was not this polite when I saw the error). So why is distutils extra raising this? Well the main reason resides in the __add_imports method in distutils extra that uses the ast module to find all the modules that you import and tries to import them to see if they are in the system. All of this is wrap by a try statement, but unfortunately the except clause looks for the common exceptions for error hen importing, and ValueError is not one of them. I have sent a patch to disutils-extra to work around this and sent a mail to python-dev asking where is the best place to submit a patch for ctypes…. Who said this project would not help open-source?

Read more