Catch events from printer in Windows - python

The printer has a print queue where are documents prepared for printing that were sent to the printer from a few computers.
I'd like to write a code in Python that runs on the server and checks for printer events. Especially when the document has finished successfully printing, I'd like to catch this event and get information about the document
document name
number of pages
format (A4, A3, etc.)
was it colorful or black and white
time of finish printing
Could you help me bounce off?
I already studied this question but I can't figure out what I need from it.
I tried this code but ends with error message:
Traceback (most recent call last):
File "...recipe-305690-1.py", line 195, in <module>
prt.EnumJobs(pJob, prt.pcbNeeded)
File "...recipe-305690-1.py", line 164, in EnumJobs
ret = ws.EnumJobsA(self.OpenPrinter(),
ctypes.ArgumentError: argument 5: <class 'OverflowError'>: int too long to convert

[GitHub]: ActiveState/code - (master) code/recipes/Python/305690_Enumerate_printer_job/recipe-305690.py (that raises ctypes.ArgumentError in your case) is ancient, some parts of it only worked by luck, and some of them never worked (as the flow didn't reach them). I submitted [GitHub]: ActiveState/code - Fixes and updates which addresses the major problems (there are some left, but the code is working).
Starting from that, I tried to tailor it for this question, and address the items (partially at least). Since the question is tagged for [GitHub]: mhammond/pywin32 - pywin32, I used it in order to have (much) shorter (and Python friendlier) code.
code00.py:
#!/usr/bin/env python
import sys
import time
import win32con as wcon
import win32print as wprn
from pprint import pprint as pp
JOB_INFO_RAW_KEYS_DICT = { # Can comment uninteresting ones
"JobId": "Id",
"Position": "Index",
"pPrinterName": "Printer",
"pUserName": "User",
"pDocument": "Document",
"TotalPages": "Pages",
"Submitted": "Created",
}
def generate_constant_strings(header, mod=None):
header_len = len(header)
ret = {}
for k, v in (globals() if mod is None else mod.__dict__).items():
if k.startswith(header):
ret[v] = k[header_len:].capitalize()
return ret
JOBSTATUS_DICT = generate_constant_strings("JOB_STATUS_", mod=wcon)
DMCOLOR_DICT = generate_constant_strings("DMCOLOR_", mod=wcon)
DMPAPER_DICT = generate_constant_strings("DMPAPER_", mod=wcon)
def printer_jobs(name, level=2):
p = wprn.OpenPrinter(wprn.GetDefaultPrinter() if name is None else name, None)
jobs = wprn.EnumJobs(p, 0, -1, level)
wprn.ClosePrinter(p)
return jobs
def job_data(job, raw_keys_dict=JOB_INFO_RAW_KEYS_DICT):
ret = {}
for k, v in job.items():
if k in raw_keys_dict:
ret[raw_keys_dict[k]] = v
ret["Format"] = DMPAPER_DICT.get(job["pDevMode"].PaperSize)
ret["Color"] = DMCOLOR_DICT.get(job["pDevMode"].Color)
ret["Status"] = JOBSTATUS_DICT.get(job["Status"])
return ret
def main(*argv):
printer_name = None
interval = 3
while 1:
try:
jobs = printer_jobs(printer_name)
for job in jobs:
data = job_data(job)
pp(data, indent=2, sort_dicts=0)
time.sleep(interval)
except KeyboardInterrupt:
break
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q070103258]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 064bit on win32
{ 'Id': 9,
'Printer': 'WF-7610 Series(Network)',
'User': 'cfati',
'Document': '*Untitled - Notepad',
'Index': 1,
'Pages': 1,
'Created': pywintypes.datetime(2021, 12, 3, 22, 52, 22, 923000, tzinfo=TimeZoneInfo('GMT Standard Time', True)),
'Format': 'A4',
'Color': 'Color',
'Status': None}
{ 'Id': 13,
'Printer': 'WF-7610 Series(Network)',
'User': 'cfati',
'Document': 'e:\\Work\\Dev\\StackOverflow\\q070103258\\code00.py',
'Index': 2,
'Pages': 4,
'Created': pywintypes.datetime(2021, 12, 3, 23, 10, 40, 430000, tzinfo=TimeZoneInfo('GMT Standard Time', True)),
'Format': 'A3',
'Color': 'Monochrome',
'Status': None}
Done.
Grapically:
Notes:
Data is retrieved using [MS.Docs]: EnumJobs function (mainly from [MS.Docs]: JOB_INFO_2 structure)
As I specified, some of the requested items are not present (finish printing time), and won't be be using this approach. For those ones, [SO]: How to catch printer event in python (#ErykSun's answer) can be used (I tried it for some events, and it works). Unfortunately PyWin32 does not wrap all the print APIs, so printer / job notification functions family (e.g. FindFirstPrinterChangeNotification) should still be called via [Python.Docs]: ctypes - A foreign function library for Python
Print jobs from other computers: this is a whole different animal. OpenPrinter function opens the printer on the local machine (by default), and only gets the local queue. I'm not sure how (or whether it's possible) to get all of them. I think that code should not need changes, only setting the computer as a print server (and pass that name as OpenPrinter's 1st argument) from [MS.Docs]: Configure Print and Document Services (but this is an untested assumption)

The problem with the code sample is that it is expecting
#164: FirstJob = c_ulong(0) #Start from this job
But the verion of the ws = WinDLL("winspool.drv") you are using expects an Int per the error message.
My recommendation is to use something like C# as handling this kind of thing is easier and better documentented.
https://www.codeproject.com/Articles/51085/Monitor-jobs-in-a-printer-queue-NET

Related

extract data from xml subfields using python

I have a xml file where I want to extract data from. I tried using python, but when I try to use an example script I found online, I can't extract the data I want.
I found a script at: https://medium.com/analytics-vidhya/parsing-xml-files-in-python-d7c136bb9aa5
it is able to extract the data from the given example, but when I try to apply this to my xml, I can't get it to work. Here is my xml file: https://pastebin.com/Q4HTYacM (it is ~200 lines long, which is why I pasted it to pastebin)
The Data I'm interested in can be found in the
<datafield tag="100" ind1="1" ind2=" "> <!--VerfasserIn-->
<subfield code="a">Ullenboom, Christian</subfield>
<subfield code="e">VerfasserIn</subfield>
<subfield code="0">(DE-588)123404738</subfield>
<subfield code="0">(DE-627)502584122</subfield>
<subfield code="0">(DE-576)184619254</subfield>
<subfield code="4">aut</subfield>
</datafield>
Field, as well as some others. The problem is, that I'm interested in <subfield code="a">Ullenboom, Christian</subfield>
but I can't get it to extract, as it seems like the root=tree.getroot() only counts the first line as a searchable line and I haven't found any way to search for the specific datafields.
Any Help is appreciated.
Edit: My Script:
## source: https://medium.com/analytics-vidhya/parsing-xml-files-in-python-d7c136bb9aa5
# import libs
import pandas as pd
import numpy as np
import glob
import xml.etree.cElementTree as et
#parse the file
tree=et.parse(glob.glob('./**/*baselinexml.xml',recursive=True)[0])
root=root.getroot()
#create list for values
creator = []
titlebook = []
VerfasserIn = []
# Converting the data
for creator in root.iter('datafield tag="100" '):
print(creator)
print("step1")
creator.append(VerfasserIn)
for titlebook in root.iter('datafield tag="245" ind1="1" ind2="0"'):
print(titlebook.text)
# creating dataframe
Jobs_df = pd.DataFrame(
list(zip(creator, titlebook)),
columns=['creator','titlebook'])
#saving as .csv
Jobs_df.to_csv("sample-api1.csv")
I'm fairly new to this kind of programming, so I tried modifying the code from the example
Listing [Python.Docs]: xml.etree.ElementTree - The ElementTree XML API, you will find everything you need to know there.
The XML you posted on PasteBin is not complete (and therefore invalid). It's lacking </zs:recordData></zs:record></zs:records></zs:searchRetrieveResponse> at the end.
The presence of namespaces makes things complicated, as the (real) node tags are not the literal strings from the file. You should insist on namespaces, and also on XPath in the above URL.
Here's a variant.
code00.py:
#!/usr/bin/env python
import sys
from xml.etree import ElementTree as ET
def main(*argv):
doc = ET.parse("./blob.xml") # Saved (and corrected) the file from PasteBin
root = doc.getroot()
namespaces = { # Manually extracted from the XML file, but there could be code written to automatically do that.
"zs": "http://www.loc.gov/zing/srw/",
"": "http://www.loc.gov/MARC21/slim",
}
#print(root)
datafield_nodes_path = "./zs:records/zs:record/zs:recordData/record/datafield" # XPath
datafield_attribute_filters = [
{
"tag": "100",
"ind1": "1",
"ind2": " ",
},
{
"tag": "245",
"ind1": "1",
"ind2": "0",
},
]
#datafield_attribute_filters = [] # Decomment this line to clear filters (and process each datafield node)
ret = []
for datafield_node in root.iterfind(datafield_nodes_path, namespaces=namespaces):
if datafield_attribute_filters:
skip_node = True
for attr_dict in datafield_attribute_filters:
for k, v in attr_dict.items():
if datafield_node.get(k) != v:
break
else:
skip_node = False
break
if skip_node:
continue
for subfield_node in datafield_node.iterfind("./subfield[#code='a']", namespaces=namespaces):
ret.append(subfield_node.text)
print("Results:")
for i, e in enumerate(ret, start=1):
print("{:2d}: {:s}".format(i, e))
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q071724477]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
Results:
1: Ullenboom, Christian
2: Java ist auch eine Insel
Done.
In the above example, I extracted the text of every subfield node (with code attribute having a value of a) which is a child of a datafield node that has the attributes matching one of the entries in the datafield_attribute_filters list (got the attributes filtering later, from your script). You can do some more filtering if you need to.

Confluent Kafka python schema parser causes conflict with fastavro

I am running Python 3.9 with Confluent Kafka 1.7.0, avro-python3 1.10.0 and fastavro 1.4.1.
The following code uses Avro schema encoder in order to encode a message, which succeeds only if we transform the resulting schema encoding by getting rid of the MappingProxyType:
from confluent_kafka import Producer
from confluent_kafka.avro import CachedSchemaRegistryClient, MessageSerializer
from fastavro.schema import parse_schema
from fastavro.validation import validate
from types import MappingProxyType
from typing import Any
import sys
def transformMap(item: Any) -> Any:
if type(item) in {dict, MappingProxyType}:
return {k:transformMap(v) for k,v in item.items()}
elif type(item) is list:
return [transformMap(v) for v in item]
else:
return item
def main(argv = None):
msgType = 'InstrumentIdMsg'
idFigi = 'BBG123456789'
head = {'sDateTime': 1, 'msgType': msgType, 'srcSeq': 1,
'rDateTime': 1, 'src': 'Brownstone', 'reqID': None,
'sequence': 1}
msgMap = {'head': head, 'product': 'Port', 'idIsin': None, 'idFigi': idFigi,
'idBB': None, 'benchmark': None, 'idCusip': None,'idCins': None}
registryClient = CachedSchemaRegistryClient(url = 'http://local.KafkaRegistry.com:8081')
schemaId, schema, version = registryClient.get_latest_schema(msgType)
serializer = MessageSerializer(registry_client = registryClient)
schemaMap = schema.to_json()
# NOTE:
# schemaMap cannot be used since it uses mappingproxy
# which causes validate() and parse_schema() to throw
schemaDict = transformMap(schemaMap)
isValid = validate(datum = msgMap, schema = schemaDict, raise_errors = True)
parsed_schema = parse_schema(schema = schemaDict)
msg = serializer.encode_record_with_schema_id(schema_id = schemaId,
record = msgMap)
producer = Producer({'bootstrap.servers': 'kafkaServer:9092'})
producer.produce(key = idFigi,
topic = 'TOPIC_NAME',
value = msg)
return 0
if __name__ == '__main__':
sys.exit(main())
The transformation basically leaves everything unchanged except altering MappingProxyType to dict instances.
Is there a problem in the way I am calling the standard library which causes mapping proxy to be used, which in turn causes fastavro to throw? Can this be fixed by something as a user, or is this really a bug in the Confluent Kafka library?
In addition, the output schemaId from registryClient.get_latest_schema() is marked in the docs to return str but returns int. If I understand correctly, this is the intended input into the schema_id parameter of serializer.encode_record_with_schema_id() (and it works correctly if I call it), which is also marked as int. Is that a typo in the docs? In other words, it seems either registryClient.get_latest_schema() should return an integer, or serializer.encode_record_with_schema_id() should take a string, or I am doing something incorrectly :) Which one is it?
Thank you very much.

How to implement NetUserEnum function in Python using only ctypes?

I'm looking to use the NetUserEnum() function in the Win32 API using Python but would like to use ctypes instead of a library that already performs this function. I have already done this with GlobalMemoryStatusEx() and GetSystemPowerStatus() but I am struggling with NetUserEnum(). I think I am getting tripped up trying to supply LPBYTE *bufptr to the function. I'm not sure how to properly structure the information and call the function correctly. Any help is appreciated.
As [SO]: How to create a Minimal, Reproducible Example (reprex (mcve)) recommends, you should post your current work together with the problem that prevents you from going further.
Before anything else, the references:
[Python.Docs]: ctypes - A foreign function library for Python
[MS.docs]: NetUserEnum function (lmaccess.h)
Yes, that LPBYTE *bufptr argument can be tricky for someone unfamiliar with CTypes (and also processing it afterwards).
code00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
from ctypes import wintypes as wt
DLL_NAME = "netapi32.dll"
FILTER_NORMAL_ACCOUNT = 0x0002
MAX_PREFERRED_LENGTH = wt.DWORD(-1)
NERR_Success = 0
NET_API_STATUS = wt.DWORD
class USER_INFO_0(ct.Structure):
_fields_ = [
("usri0_name", wt.LPWSTR),
]
def user_info_type(level):
if level == 0:
return USER_INFO_0
# elif ...
else:
print("Invalid user info level")
def print_user_info(idx, user_info):
print("\nUser {:d}:".format(idx))
if isinstance(user_info, USER_INFO_0):
print(" Name: {:s}".format(user_info.usri0_name))
# elif ...
else:
print("Invalid data")
def main(*argv):
netapi32 = ct.WinDLL(DLL_NAME)
NetUserEnum = netapi32.NetUserEnum
NetUserEnum.argtypes = (wt.LPCWSTR, wt.DWORD, wt.DWORD, ct.POINTER(wt.LPBYTE), wt.DWORD, wt.LPDWORD, wt.LPDWORD, wt.PDWORD)
NetUserEnum.restype = NET_API_STATUS
NetApiBufferFree = netapi32.NetApiBufferFree
NetApiBufferFree.argtypes = (wt.LPVOID,)
NetApiBufferFree.restype = NET_API_STATUS
info_level = 0
filter_flag = FILTER_NORMAL_ACCOUNT
buf = wt.LPBYTE()
buf_len = MAX_PREFERRED_LENGTH
read, total = wt.DWORD(0), wt.DWORD(0)
resume_handle = None
res = NetUserEnum(None, info_level, filter_flag, ct.byref(buf), buf_len, ct.byref(read), ct.byref(total), resume_handle)
print("\n{0:s} returned: {1:d}".format(NetUserEnum.__name__, res))
if res != NERR_Success:
return -1
print("{:d} (out of {:d}) entries read".format(read.value, total.value))
UserInfoArray = user_info_type(info_level) * read.value
users = UserInfoArray.from_address(ct.addressof(buf.contents))
for idx, user in enumerate(users):
print_user_info(idx, user)
res = NetApiBufferFree(buf)
if res != NERR_Success:
print("Error freing buffer: {:d}".format(res))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Notes:
This is an minimum minimorum example to get you past your current problem. There are certain aspects that need to be taken into consideration :
If you want more details about each user, use the appropriate USER_INFO_* structure. You should define it (as I did for USER_INFO_0), and also handle it (in the wrapper functions that I wrote: user_info_type, print_user_info), and (obviously) set the appropriate level (info_level)
This minimal example is OK for a regular computer. But, if you attempt to run it in a domain (with tenths of thousands of users) you might experience some memory issues. In that case, you should set a decent buf_size (maybe 16 MiBs) and call NetUserEnum (and also NetApiBufferFree) in a loop, relying on resume_handle, while it keeps returning ERROR_MORE_DATA
Make sure the user that runs this program has enough privileges
Output:
cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q066185045]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 64bit on win32
NetUserEnum returned: 0
6 (out of 6) entries read
User 0:
Name: Administrator
User 1:
Name: cfati
User 2:
Name: DefaultAccount
User 3:
Name: Guest
User 4:
Name: test
User 5:
Name: WDAGUtilityAccount
Done.

How to use SHGetFileInfo with SHGFI_PIDL in python

I'm trying to retrieve file information (specifically info about the icon) using SHGetFileInfo. In reality, I don't have the full path of the file, I only have the pidl.
The following code returns (0L, (0, 0, 0, '', '')) and my question is why.
from win32com.shell import shell, shellcon
def get_info():
desktop = shell.SHGetDesktopFolder()
eaten, desktop_pidl, attr = desktop.ParseDisplayName(None, None, r"C:\Users\Ella\Desktop")
return shell.SHGetFileInfo(desktop_pidl, 0, shellcon.SHGFI_PIDL | shellcon.SHGFI_SYSICONINDEX | shellcon.SHGFI_ICON | shellcon.SHGFI_DISPLAYNAME)
On the other hand, the code bellow does work for some reason (it uses full path instead of pidl):
from win32com.shell import shell, shellcon
def get_info2():
return shell.SHGetFileInfo(r"C:\Users\Ella\Desktop", 0, shellcon.SHGFI_SYSICONINDEX | shellcon.SHGFI_ICON | shellcon.SHGFI_DISPLAYNAME)
Thanks!
You've uncovered a bug in PySHGetFileInfo. If SHGFI_PIDL is set in flags, it calls PyObject_AsPIDL and stores the result to pidl_or_name, but it mistakenly passes name to SHGetFileInfo, which in this case is the initial NULL value. See below for more details.
You asked how to set a breakpoint on shell32!SHGetFileInfoW. There's no simple answer to that. Instead allow me to share an overview of what I did to test this. Hopefully this will at least get you started.
Test environment:
64-bit Windows 7 SP1 (6.1.7601)
Windows SDK 7.1 (ensure the debuggers are installed)
Visual Studio 2010 SP1
Visual C++ 2010 SP1 Compiler Update
Python 3.4 (and debug files)
Mercurial (hg.exe, not TortoiseHg)
Set up the shell environment.
"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.Cmd"
set MSSdk=%WindowsSDKDir%
set SYMDIR=C:\Symbols
set SYMSRV=http://msdl.microsoft.com/download/symbols
set _NT_SYMBOL_PATH=symsrv*symsrv.dll*%SYMDIR%*%SYMSRV%
path C:\Program Files\Debugging Tools for Windows (x64);%PATH%
path C:\Program Files\Mercurial;%PATH%
Create a Python virtual environment.
py -3.4 -m venv --symlinks test
venv doesn't link the .pdb files, so grab those manually in a for loop.
set PYDIR="%ProgramW6432%\Python34"
set CMD=mklink "test\Scripts\%~nxf" "%f"
for /R %PYDIR% %f in (*.pdb) do #%CMD%
Activate the virtual environment.
test\Scripts\activate
Clone the PyWin32 repo. Build and install version 219.
set HGSRV=http://pywin32.hg.sourceforge.net
hg clone %HGSRV%/hgroot/pywin32/pywin32
cd pywin32
hg up b219
I edited setup.py to comment out everything related to building
win32com.mapi. My setup didn't even have the required headers,
and when I obtained them there were problems building the
extension for WIN64.
Build and install the package.
python setup3.py install
Run Python under the console debugger, cdb.exe.
>cdb -xi ld python
Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: python
Symbol search path is: symsrv*symsrv.dll*C:\Symbols*
http://msdl.microsoft.com/download/symbols
Executable search path is:
(d50.1174): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00000000`770bcb70 cc int 3
0:000> bp shell32!SHGetFileInfoW
0:000> g
Python 3.4.2 (v3.4.2:ab2c023a9432, Oct 6 2014, 22:16:31)
[MSC v.1600 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
The option -xi ld in the above command line sets a filter to ignore printing loaded modules. There are lots of tutorials and 'cheat sheets' online for using Microsoft's debuggers such as WinDbg, cdb, and kd. The debuggers all use the same engine, so they support a common set of debugging commands.
The attached debugger has a breakpoint set on shell32!SHGetFileInfoW. When the breakpoint is triggered, the debugger grabs the console. One of the few redeeming features of the Windows console is its per-application input history and aliases. This makes it convenient to recall commands when bouncing in and out of the debugger and debuggee in the same console window.
>>> import os
>>> from win32com.shell import shell, shellcon
>>> print(shell.__file__)
C:\Temp\test\lib\site-packages\win32comext\shell\shell.pyd
>>> path = os.path.expanduser(r'~\Desktop\desktop.ini')
>>> pidl = shell.SHParseDisplayName(path, 0, None)[0]
>>> flags = (shellcon.SHGFI_PIDL |
... shellcon.SHGFI_SYSICONINDEX |
... shellcon.SHGFI_ICON |
... shellcon.SHGFI_DISPLAYNAME)
>>> shell.SHGetFileInfo(pidl, 0, flags)
Breakpoint 0 hit
SHELL32!SHGetFileInfoW:
000007fe`fd692290 fff3 push rbx
0:000> k 5
*** WARNING: Unable to verify checksum for
C:\Temp\test\lib\site-packages\win32comext\shell\shell.pyd
Child-SP RetAddr Call Site
00000000`003ff2d8 00000000`5f44c5e8 SHELL32!SHGetFileInfoW
00000000`003ff2e0 00000000`5f5af8bd shell!PySHGetFileInfo+0xf8
00000000`003ff610 00000000`5f62385b python34!PyCFunction_Call+0x12d
00000000`003ff640 00000000`5f625c89 python34!call_function+0x2ab
00000000`003ff6a0 00000000`5f62770c python34!PyEval_EvalFrameEx+0x2279
0:000> r rcx
rcx=0000000000000000
0:000> g
(0, (0, 0, 0, '', ''))
In the Windows x64 ABI, the first argument of a function is passed in register rcx. We know from the SHGetFileInfo docs that this should be the PIDL, but actually NULL is being passed. Clearly this is a bug. The stack trace lays the blame on shell!PySHGetFileInfo. Here's a snippet of the problematic code:
if (flags & SHGFI_PIDL) {
ok = PyObject_AsPIDL(obName, &pidl, FALSE);
pidl_or_name = (TCHAR *)pidl;
} else {
ok = PyWinObject_AsTCHAR(obName, &name, FALSE);
pidl_or_name = name;
}
if (!ok)
return NULL;
SHFILEINFO info;
memset(&info, 0, sizeof(info));
info.dwAttributes = info_attrs;
PY_INTERFACE_PRECALL;
DWORD_PTR dw = SHGetFileInfo(name, attr, &info, sizeof(info), flags);
The mistake is passing name as the first argument instead of pidl_or_name.
The question is tagged ctypes. IMO, using ctypes is worth it if doing so eliminates a large dependency such as PyWin32. I wouldn't normally use ctypes by itself for a COM-based API. The comtypes package builds on ctypes if you want to try that. In this case directly calling COM methods can be avoided by instead calling SHParseDisplayName. Other than using HRESULT return codes, it's pretty much like any other Win32 API.
import types as _types
import ctypes as _ctypes
from ctypes import wintypes as _wtypes
_mtypes = _types.ModuleType('_mtypes')
_ole32 = _ctypes.WinDLL('ole32')
_shell32 = _ctypes.WinDLL('shell32')
_user32 = _ctypes.WinDLL('user32')
try:
from win32com.shell import shell as _shell
except ImportError:
_shell = None
try:
from win32com.shell import shellcon
except ImportError:
shellcon = _types.ModuleType('shellcon')
shellcon.SHGFI_LARGEICON = 0x00000
shellcon.SHGFI_SMALLICON = 0x00001
shellcon.SHGFI_OPENICON = 0x00002
shellcon.SHGFI_SHELLICONSIZE = 0x00004
shellcon.SHGFI_PIDL = 0x00008
shellcon.SHGFI_USEFILEATTRIBUTES = 0x00010
shellcon.SHGFI_ICON = 0x00100
shellcon.SHGFI_DISPLAYNAME = 0x00200
shellcon.SHGFI_TYPENAME = 0x00400
shellcon.SHGFI_ATTRIBUTES = 0x00800
shellcon.SHGFI_ICONLOCATION = 0x01000
shellcon.SHGFI_EXETYPE = 0x02000
shellcon.SHGFI_SYSICONINDEX = 0x04000
shellcon.SHGFI_LINKOVERLAY = 0x08000
shellcon.SHGFI_SELECTED = 0x10000
shellcon.SHGFI_ATTR_SPECIFIED = 0x20000
try:
import win32con
except ImportError:
win32con = _types.ModuleType('win32con')
win32con.MAX_PATH = 260
win32con.FILE_ATTRIBUTE_READONLY = 0x00001
win32con.FILE_ATTRIBUTE_HIDDEN = 0x00002
win32con.FILE_ATTRIBUTE_SYSTEM = 0x00004
win32con.FILE_ATTRIBUTE_DIRECTORY = 0x00010
win32con.FILE_ATTRIBUTE_ARCHIVE = 0x00020
win32con.FILE_ATTRIBUTE_DEVICE = 0x00040
win32con.FILE_ATTRIBUTE_NORMAL = 0x00080
win32con.FILE_ATTRIBUTE_TEMPORARY = 0x00100
win32con.FILE_ATTRIBUTE_ATOMIC_WRITE = 0x00200
win32con.FILE_ATTRIBUTE_SPARSE_FILE = 0x00200
win32con.FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
win32con.FILE_ATTRIBUTE_XACTION_WRITE = 0x00400
win32con.FILE_ATTRIBUTE_COMPRESSED = 0x00800
win32con.FILE_ATTRIBUTE_OFFLINE = 0x01000
win32con.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x02000
win32con.FILE_ATTRIBUTE_ENCRYPTED = 0x04000
win32con.FILE_ATTRIBUTE_VIRTUAL = 0x10000
_mtypes.CData = _ctypes.Array.__bases__[0]
_mtypes.PPIDLIST_ABSOLUTE = _ctypes.POINTER(_ctypes.c_void_p)
_mtypes.SFGAOF = _wtypes.ULONG
_mtypes.PSFGAOF = _ctypes.POINTER(_mtypes.SFGAOF)
_ole32.CoInitialize.restype = _ctypes.HRESULT # checked
_ole32.CoInitialize.argtypes = (_ctypes.c_void_p,)
_ole32.CoUninitialize.restype = None
_ole32.CoUninitialize.argtypes = ()
_ole32.CoTaskMemFree.restype = None
_ole32.CoTaskMemFree.argtypes = (_ctypes.c_void_p,)
_user32.DestroyIcon.argtypes = (_wtypes.HICON,)
_shell32.SHParseDisplayName.restype = _ctypes.HRESULT # checked
_shell32.SHParseDisplayName.argtypes = (
_wtypes.LPCWSTR, # pszName, _In_
_ctypes.c_void_p, # pbc, _In_opt_
_mtypes.PPIDLIST_ABSOLUTE, # ppidl, _Out_
_mtypes.SFGAOF, # sfgaoIn, _In_
_mtypes.PSFGAOF) # psfgaoOut, _Out_opt_
class SHFILEINFO(_ctypes.Structure):
_fields_ = (('hIcon', _wtypes.HICON),
('iIcon', _ctypes.c_int),
('dwAttributes', _wtypes.DWORD),
('szDisplayName', _wtypes.WCHAR * win32con.MAX_PATH),
('szTypeName', _wtypes.WCHAR * 80))
_mtypes.SHFILEINFO = SHFILEINFO
_mtypes.PSHFILEINFO = _ctypes.POINTER(SHFILEINFO)
_shell32.SHGetFileInfoW.restype = _ctypes.c_void_p
_shell32.SHGetFileInfoW.argtypes = (
_wtypes.LPVOID, # pszPath, _In_
_wtypes.DWORD, # dwFileAttributes,
_mtypes.PSHFILEINFO, # psfi, _Inout_
_wtypes.UINT, # cbFileInfo,
_wtypes.UINT) # uFlags
def SHGetFileInfo(pidl, attributes=0, flags=0):
if _shell is not None:
if not isinstance(pidl, (str, bytes, _mtypes.CData)):
pidl = _shell.PIDLAsString(pidl)
finfo = SHFILEINFO()
_ole32.CoInitialize(None)
try:
retval = _shell32.SHGetFileInfoW(pidl,
attributes,
_ctypes.byref(finfo),
_ctypes.sizeof(finfo),
flags)
finally:
_ole32.CoUninitialize()
if not retval:
if flags != shellcon.SHGFI_EXETYPE:
raise _ctypes.WinError()
return retval, finfo
Example:
if __name__ == '__main__':
import os
path = os.path.expanduser(r'~\Desktop\desktop.ini')
pidl = _shell.SHParseDisplayName(path, 0)[0]
assert isinstance(pidl, list)
flags = (shellcon.SHGFI_PIDL |
shellcon.SHGFI_ICON |
shellcon.SHGFI_DISPLAYNAME |
shellcon.SHGFI_TYPENAME |
shellcon.SHGFI_ATTRIBUTES |
shellcon.SHGFI_SYSICONINDEX)
hImageList, finfo = SHGetFileInfo(pidl, 0, flags)
print('hImageList:', hImageList)
for name, typ in finfo._fields_:
print(name, ': ', ascii(getattr(finfo, name)), sep='')
if finfo.hIcon:
_user32.DestroyIcon(finfo.hIcon)
Output:
hImageList: 4411024
hIcon: 10617107
iIcon: 7
dwAttributes: 1078497655
szDisplayName: 'desktop.ini'
szTypeName: 'Configuration settings'

win32pdh.EnumObjectItems call error

I'm running Python 2.7 in Windows 7, with pywin32-216-win32-py2.7 installed. I'm running the following code, and it runs fine on one computer but outputs an error on another (both Win7, Py2.7, same pywin library).
Error message:
File "C:\Energy\Support Sheets\Kill Excel.py", line 9, in GetProcessID
items, instances = win32pdh.EnumObjectItems( None, None, object, win32pdh.PERF_DETAIL_WIZARD )
error: (-1073738824, 'EnumObjectItems for buffer size', 'The specified object was not found on the computer.')
Full code:
import win32api, win32con
import win32pdh
import os
import signal
import time
def GetProcessID( name ) :
object = "Process"
items, instances = win32pdh.EnumObjectItems( None, None, object, win32pdh.PERF_DETAIL_WIZARD )
val = None
if name in instances :
hq = win32pdh.OpenQuery()
hcs = [ ]
item = "ID Process"
path = win32pdh.MakeCounterPath( ( None, object, name, None, 0, item ) )
hcs.append( win32pdh.AddCounter( hq, path ) )
win32pdh.CollectQueryData( hq )
time.sleep( 0.01 )
win32pdh.CollectQueryData( hq )
for hc in hcs:
type, val = win32pdh.GetFormattedCounterValue( hc, win32pdh.PDH_FMT_LONG )
win32pdh.RemoveCounter( hc )
win32pdh.CloseQuery( hq )
return val
def Kill_Process_pid(pid):
handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid) #get process handle
win32api.TerminateProcess(handle, -1) #kill by handle
win32api.CloseHandle(handle) #close api
def Kill_Process(name):
pid = GetProcessID(name)
if pid:
try:
Kill_Process_pid(pid)
return True
except:
pass
else:
return False
# MAIN FUNCTION
print 'Killing Excel instances...',
while Kill_Process('EXCEL'):
time.sleep(0.2)
print 'Done!'
I had the same issue when the Process performance counter was disabled - apparently this can just happen. There are instructions here for how to re-enable the counters. If you can't install software on the machine, you can run regedit and search for keys called "Disable Performance Counters" and delete them.
After that, you may need to run "lodctr /R" on a cmd shell as Administrator to reload the performance counters before it will work.

Categories

Resources