Introduction
As I was perusing the MSDN for another unrelated reason, I stumbled across the IDiskQuotaControl interface. While it sounded interesting, my lack of COM experience forced me to press on with my original goal. Several months later I decided to explore the IDiskQuotaControl
interface again. What I discovered along the way was very helpful, at least to me.
Raw interface pointers
I started out just including the dskquota.h header file. To make use of the interfaces and properties in this file, I used a code similar to:
IDiskQuotaControl *pDiskQuotaControl;
IEnumDiskQuotaUsers *pEnumDiskQuotaUsers;
HRESULT hr;
hr = CoCreateInstance(CLSID_DiskQuotaControl,
NULL,
CLSCTX_INPROC_SERVER,
IID_IDiskQuotaControl,
(PVOID *) &pDiskQuotaControl);
if (SUCCEEDED(hr))
{
hr = pDiskQuotaControl->Initialize(_bstr_t("c:\\"), TRUE);
if (SUCCEEDED(hr))
{
hr = pDiskQuotaControl->CreateEnumUsers(NULL,
0, DISKQUOTA_USERNAME_RESOLVE_SYNC,
&pEnumDiskQuotaUsers);
if (SUCCEEDED(hr))
{
...
pEnumDiskQuotaUsers->Release();
}
}
pDiskQuotaControl->Release();
}
Cool, I thought. I wonder if I could make that CoCreateInstance()
call a bit cleaner. At this point I remembered reading about smart pointers from a COM project I worked on several years ago. Looking back at that code as a starting point, I put the necessary changes in place.
Smart pointers
First I imported the disk quota type library contained within dskquota.dll:
#import <dskquota.dll> rename("DiskQuotaTypeLibrary", "DQ")
I renamed the namespace simply because it was so long. Now I was ready to change the raw interface pointers to smart pointers. I started out with something simple:
DQ::DIDiskQuotaControlPtr ptrDiskQuotaControl;
HRESULT hr =
ptrDiskQuotaControl.CreateInstance(__uuidof(DQ::DiskQuotaControl));
if (SUCCEEDED(hr))
{
hr = ptrDiskQuotaControl->Initialize(_bstr_t("c:\\"), TRUE);
if (NOERROR == hr)
{
...
}
}
Wow, this works too. I went on and tried to use the CreateEnumUsers()
method. That's when things went downhill. First, that method did not exist. Second, the fourth parameter was a IEnumDiskQuotaUsers smart pointer which also did not exist. Now I was really stumped, especially since the MSDN documentation indicated otherwise. I poked at it a while longer before asking for help. I was told that it looks like a type library which only has dispatch interfaces, but that I could still use smart pointers if I wanted. The only changes I'd need were to remove the #import
statement, and add my own types using the _COM_SMARTPTR_TYPEDEF()
macro:
_COM_SMARTPTR_TYPEDEF(IDiskQuotaControl, IID_IDiskQuotaControl);
_COM_SMARTPTR_TYPEDEF(IEnumDiskQuotaUsers, IID_IEnumDiskQuotaUsers);
_COM_SMARTPTR_TYPEDEF(IDiskQuotaUser, IID_IDiskQuotaUser);
Now I was making some progress! I could create an instance of the IDiskQuotaControl
interface, initialize it, and call the CreateEnumUsers()
method just like I had when using the raw interface pointers:
IEnumDiskQuotaUsersPtr ptrEnumDQUsers;
hr = ptrDiskQuotaControl->CreateEnumUsers(NULL,
0,
DISKQUOTA_USERNAME_RESOLVE_SYNC,
&ptrEnumDQUsers);
if (NOERROR == hr)
{
IDiskQuotaUserPtr ptrDQUser;
hr = ptrEnumDQUsers->Next(1, &ptrDQUser, NULL);
if (NOERROR == hr)
{
...
}
}
Just to ensure that I was indeed able to enumerate through each of the registered users, I used:
TCHAR szLogonName[128];
szDisplayname[128];
while (ptrEnumDQUsers->Next(1, &ptrDQUser, NULL) == NOERROR)
{
hr = ptrDQUser->GetName(NULL,
0,
szLogonName,
sizeof(szLogonName),
szDisplayName,
sizeof(szDisplayName));
if (NOERROR == hr)
TRACE(_T("Logon name = ]%s[\n"), szLogonName);
}
Yep, it worked! My next goal was to somehow wrap all this up into a useful class or two to hide some of the COM-ness from it.
The CDiskQuotaControl class
This class is a wrapper around most of the IDiskQuotaControl
and IEnumDiskQuotaUsers
interfaces. Most of the text comes straight from the MSDN.
CDiskQuotaControl |
|
Constructs a CDiskQuotaControl object. |
Initialize |
|
Initializes a new CDiskQuotaControl object by opening the NTFS volume in read-only mode. The return value indicates whether the volume supports NTFS disk quotas and whether the caller has sufficient access rights. |
EnumFirstUser |
|
Creates an enumerator object for enumerating quota users on the volume, and then retrieves the first user in the enumeration sequence. The user's account information is resolved synchronously. |
EnumNextUser |
|
Retrieves the next user in the enumeration sequence. |
GetDefaultQuotaLimit |
|
Retrieves the default quota limit for the volume. |
GetDefaultQuotaLimitText |
|
Retrieves the default quota limit for the volume. The limit is expressed as a text string, for example "10.5 MB". If the volume has no limit, the string returned is "No Limit." If the buffer is too small, it is truncated to fit the buffer. |
GetDefaultQuotaThreshold |
|
Retrieves the default quota warning threshold for the volume. |
GetDefaultQuotaThresholdText |
|
Retrieves the default warning threshold for the volume. This threshold is expressed as a text string, for example "10.5 MB". If the volume does not have a threshold, the string returned is "No Limit." If the buffer is too small, the string is truncated to fit the buffer. |
IsStateDisabled |
|
Determines if quotas are enabled on the volume. |
IsStateTracked |
|
Determines if quotas are tracked (i.e., the limit is not enforced) on the volume. |
IsStateEnforced |
|
Determines if quotas are enforced on the volume. |
IsStateIncomplete |
|
Determines if the volume's quota information is out of date. |
IsStateRebuilding |
|
Determines if the volume's quota information is being rebuilt. |
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_FILE_NOT_FOUND |
|
The requested file or object was not found. |
|
ERROR_INITIALIZED |
|
The controller object has already been initialized. Multiple initialization is not allowed. |
|
ERROR_INVALID_NAME |
|
The requested file path is invalid. |
|
ERROR_NOTSUPPORTED |
|
The file system does not support quotas. |
|
ERROR_PATH_NOT_FOUND |
|
The requested file path was not found. |
Parameters
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_NOT_READY |
|
The CDiskQuotaControl object is not initialized. |
|
E_INVALIDARG |
|
The pDiskQuotaUser parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
|
S_FALSE |
|
If the first, or next, user in the enumeration sequence was not successfully retrieved. |
Parameters
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_NOT_READY |
|
The CDiskQuotaControl object is not initialized. |
|
E_INVALIDARG |
|
The pLimit parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
HRESULT GetDefaultQuotaLimitText( PTSTR pszDefaultQuotaText, const DWORD dwSize ) const
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_NOT_READY |
|
The CDiskQuotaControl object is not initialized. |
|
E_INVALIDARG |
|
The pszDefaultQuotaText parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
strDefaultQuotaText
Reference to a CString
object that will receive the text string.
pszDefaultQuotaText
Pointer to the buffer to receive the text string.
dwSize
Size of the buffer, in characters.
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_NOT_READY |
|
The CDiskQuotaControl object is not initialized. |
|
E_INVALIDARG |
|
The pThreshold parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
HRESULT GetDefaultQuotaThresholdText( PTSTR pszDefaultQuotaText, const DWORD dwSize ) const
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_NOT_READY |
|
The CDiskQuotaControl object is not initialized. |
|
E_INVALIDARG |
|
The pszDefaultQuotaText parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
strDefaultQuotaText
Reference to a CString
object that will receive the text string.
pszDefaultQuotaText
Pointer to the buffer to receive the text string.
dwSize
Size of the buffer, in characters.
Return value
true
, if quotas are disabled on the volume; false
otherwise.
Return value
true
, if quotas are enabled on the volume but the limit is not being enforced; false
otherwise.
Return value
true
, if quotas are enabled on the volume and the limit is being enforced; false
otherwise.
Return value
true
, if the quota's information is out of date; false
otherwise.
Return value
true
, if the quota's information is being rebuilt; false
otherwise.
The CDiskQuotaUser class
This class is a wrapper around most of the IDiskQuotaUser
interface. Most of the text comes straight from the MSDN.
CDiskQuotaUser |
|
Constructs a CDiskQuotaUser object which represents a single user quota entry in the volume quota information file. |
GetName |
|
Retrieves the name strings associated with a disk quota user. |
GetQuotaThresholdText |
|
Retrieves the user's warning threshold for the volume. This threshold is expressed as a text string, for example "10.5 MB". If the user's threshold is unlimited, the string returned is "No Limit." If the destination buffer is too small, the string is truncated to fit the buffer. |
GetQuotaThreshold |
|
Retrieves the user's warning threshold value on the volume. The threshold is an arbitrary value set by the volume's quota administrator. You can use it to identify users who are approaching their hard quota limit. |
GetQuotaLimitText |
|
Retrieves the user's quota limit for the volume. This limit is expressed as a text string, for example "10.5 MB". If the user has no quota limit, the string returned is "No Limit." If the destination buffer is too small, the string is truncated to fit the buffer. |
GetQuotaLimit |
|
Retrieves the user's quota limit value on the volume. The limit is set as the maximum amount of disk space available to the volume user. |
GetQuotaUsedText |
|
Retrieves the user's quota used value for the volume. This value is expressed as a text string, for example "10.5 MB". If the destination buffer is too small, the string is truncated to fit the buffer. |
GetQuotaUsed |
|
Retrieves the user's quota used value on the volume. This is the amount of information stored on the volume by the user. Note that this is the amount of uncompressed information. Therefore, the use of NTFS compression does not affect this value. |
GetQuotaInformation |
|
Retrieves the values for the user's warning threshold, hard quota limit, and quota used. |
IsStatusResolved |
|
Determines if a user's SID resolved to a user account. |
IsStatusUnavailable |
|
Determines if a user's account is available. The network DC may not be available. |
IsStatusDeleted |
|
Determines if a user's account was deleted from the domain. |
IsStatusInvalid |
|
Determines if a user's account is invalid. |
IsStatusUnknown |
|
Determines if a user's account is unknown. |
IsStatusUnresolved |
|
Determines if a user's SID did not resolve to a user account. |
HRESULT GetName( PTSTR pszLogonName, const DWORD dwSizeLogon, PTSTR pszDisplayName, const DWORD dwSizeDisplay ) const;
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
Parameters
strLogonName
Reference to a CString
object that will receive the name the user specified to log on to the computer. The format of the name returned depends on whether directory service information is available.
strDisplayName
Reference to a CString
object that will receive the display name for the quota user. If the information is not available, the string returned is empty.
pszLogonName
Pointer to the buffer to receive the name the user specified to log on to the computer. This value can be NULL
. The format of the name returned depends on whether directory service information is available.
dwSizeLogon
Size of the logon name buffer, in characters. Ignored if pszLogonName
is NULL
.
pszDisplayName
Pointer to the buffer to receive the display name for the quota user. This value can be NULL
. If the information is not available, the string returned is of zero length.
dwSizeDisplay
Size of the display-name buffer, in characters. Ignored if pszDisplayName
is NULL
.
HRESULT GetQuotaThresholdText( PTSTR pszQuotaThresholdText, const DWORD dwSize ) const;
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pszQuotaThresholdText parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
strQuotaThresholdText
Reference to a CString
object that will receive the text string.
pszQuotaThresholdText
Pointer to the buffer to receive the text string.
dwSize
Size of the destination buffer, in characters.
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pLimit parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
HRESULT GetQuotaLimitText( PTSTR pszQuotaLimitText, const DWORD dwSize ) const;
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pszQuotaLimitText parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
strQuotaLimitText
Reference to a CString
object that will receive the text string.
pszQuotaLimitText
Pointer to the buffer to receive the text string.
dwSize
Size of the buffer, in characters.
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pLimit parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
HRESULT GetQuotaUsedText( PTSTR pszQuotaUsedText, const DWORD dwSize ) const;
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pszQuotaUsedText parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
strQuotaUsedText
Reference to a CString
object that will receive the text string.
pszQuotaUsedText
Pointer to the buffer to receive the text string.
dwSize
Size of the buffer, in bytes.
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pUsed parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
Return value
|
Value |
|
Meaning |
|
NOERROR |
|
Success. |
|
ERROR_ACCESS_DENIED |
|
The caller has insufficient access rights. |
|
ERROR_LOCK_FAILED |
|
Failure to obtain an exclusive lock. |
|
E_INVALIDARG |
|
The pDQUserInfo parameter is NULL . |
|
E_OUTOFMEMORY |
|
Insufficient memory. |
|
E_FAIL |
|
An unexpected file system error occurred. |
|
E_UNEXPECTED |
|
An unexpected exception occurred. |
Parameters
pDQUserInfo
Pointer to a DISKQUOTA_USER_INFORMATION
structure to receive the quota information.
dwSize
Size of the quota information structure, in bytes.
Return value
true
if the user's SID was resolved to a user account; false
otherwise.
Return value
true
if the user's account is unavailable (e.g., the domain controller may not be available); false
otherwise.
Return value
true
if the user's account was deleted; false
otherwise.
Return value
true
if the user's account is invalid; false
otherwise.
Return value
true
if the user's account is unknown; false
otherwise.
Return value
true
if the user's SID does not resolve to a user account; false
otherwise.
Example
CDiskQuotaControl DiskQuotaControl;
HRESULT hr = DiskQuotaControl.Initialize(strVolume);
if (ERROR_NOT_SUPPORTED == hr)
TRACE(CString((LPCTSTR) IDS_NOT_SUPPORTED));
if (NOERROR == hr)
{
if (DiskQuotaControl.IsStateDisabled())
TRACE(CString((LPCTSTR) IDS_NOT_ENABLED));
else if (DiskQuotaControl.IsStateTracked())
TRACE(CString((LPCTSTR) IDS_NOT_TRACKED));
else if (DiskQuotaControl.IsStateEnforced())
TRACE(CString((LPCTSTR) IDS_NOT_ENFORCED));
else if (DiskQuotaControl.IsStateIncomplete())
TRACE(CString((LPCTSTR) IDS_INCOMPLETE));
else if (DiskQuotaControl.IsStateRebuilding())
TRACE(CString((LPCTSTR) IDS_REBUILDING));
else
TRACE(CString((LPCTSTR) IDS_UNKNOWN));
DISKQUOTA_USER_INFORMATION DQUserInfo;
CString strLogonName,
strDisplayName,
strQuotaThresholdText,
strQuotaLimitText,
strQuotaUsedText;
hr = DiskQuotaControl.EnumFirstUser(&DiskQuotaUser);
while (S_OK == hr)
{
DiskQuotaUser.GetName(strLogonName, strDisplayName);
DiskQuotaUser.GetQuotaThresholdText(strQuotaThresholdText);
DiskQuotaUser.GetQuotaLimitText(strQuotaLimitText);
DiskQuotaUser.GetQuotaUsedText(strQuotaUsedText);
DiskQuotaUser.GetQuotaInformation(&DQUserInfo,
sizeof(DISKQUOTA_USER_INFORMATION));
if (DQUserInfo.QuotaUsed > DQUserInfo.QuotaLimit &&
DQUserInfo.QuotaLimit >= 0)
TRACE(CString((LPCTSTR) IDS_ABOVE_LIMIT));
else if (DQUserInfo.QuotaUsed > DQUserInfo.QuotaThreshold &&
DQUserInfo.QuotaThreshold >= 0)
TRACE(CString((LPCTSTR) IDS_WARNING));
else
TRACE(CString((LPCTSTR) IDS_OK));
TRACE(strLogonName);
hr = DiskQuotaControl.EnumNextUser(&DiskQuotaUser);
}
}
Extras
The code in this section isn't within the scope of this article but I wanted to show it anyway as it was useful for me and hopefully for anyone else who might need it.
I've seen several different varieties of adjusting a control's dimensions or location at runtime. Some examples were painfully long while others were not as bad or were just plain hard to follow. I put a simple function together that I feel is small and easy to understand. This function is called from the dialog's OnSize()
method.
In the dialog's OnInitDialog()
method, we need to get the initial size of the client area. This is so we have a reference point from which to move. This looks like:
GetClientRect(m_rectOrig);
m_nWidth = m_rectOrig.Width();
m_nHeight = m_rectOrig.Height();
Notice that m_rectOrig
is a member variable rather than just a local variable. It is used in the OnGetMinMaxInfo()
method to make sure we don't minimize the dialog too much.
The function to do the sizing and moving of the child controls looks like:
void RepositionChildControl( CWnd *pWnd, const int dx,
const int dy, const UINT uAnchor )
{
if (NULL != pWnd->m_hWnd)
{
CRect rect;
pWnd->GetWindowRect(rect);
ScreenToClient(rect);
if (uAnchor & resizeVERT)
rect.bottom += dy;
else if (uAnchor & anchorBOTTOM)
rect.OffsetRect(0, dy);
if (uAnchor & resizeHORZ)
rect.right += dx;
else if (uAnchor & anchorRIGHT)
rect.OffsetRect(dx, 0);
pWnd->MoveWindow(rect);
}
}
Whenever the dialog is being resized, the OnSize()
method is called. Here is where we call RepositionChildControl()
:
void OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
int nWidthOffset = cx - m_nWidth;
int nHeightOffset = cy - m_nHeight;
RepositionChildControl(&m_lblStatus, nWidthOffset,
nHeightOffset, anchorTOP | anchorRIGHT | resizeHORZ);
RepositionChildControl(&m_lcDQInfo, nWidthOffset,
nHeightOffset, anchorTOP | anchorLEFT | resizeBOTH);
RepositionChildControl(&m_btnCancel, nWidthOffset,
nHeightOffset, anchorTOP | anchorRIGHT);
m_nWidth = cx;
m_nHeight = cy;
}
Since this type of movement does cause a little bit of flicker, the interested reader could add each of the controls to a CWnd*
array. Then in the dialog's OnEraseBkgnd()
method, subtract the rect
of each of the controls from the dialog's rect
using CDC::ExcludeClipRect()
.
Summary
These two classes are far from complete. They reflect the "read" methods of each of the interfaces, thus they do not update any quota information. However, they could easily be expanded for such functionality.
Enjoy!