I have a C++ application that needs to retrieve an IIS 7 site's properties (such as metabase properties similar to those in IIS6 - Path, AppFriendlyName etc).
With IIS 7, my code does this:
Get the AppHostWritableAdminManager and commit the path MACHINE/WEBROOT/APPHOST/Default Web Site/.
Call GetAdminSection with the section name appSettings.
Then look at the returned collection and look for the property (Path for example).
This works in IIS 6 but not on IIS7/7.5.
What changes do I need to make in order to make this work?
In IIS7 the configuration data is not stored in a "metabase" and also the metabase properties that we're accustomed to in IIS6 aren't the same. IIS7 stores the bulk of its configuration data in the following file:
%systemroot%\System32\InetSrv\Config\applicationHost.config
There are other files, but for the purposes of answering this question, this is the file we're interested in.
The documentation for applicationHost.config can be found here:
<system.applicationHost> - IIS.NET
configuration Element [IIS 7 Settings Schema]
system.applicationHost Section Group [IIS 7 Settings Schema]
You can find a list of IIS6 metabase -> IIS7 XML configuration mappings here:
Converting Metabase Properties to Configuration Settings [IIS 7]
For example in IIS6 the path to a site's /root is stored in the Path attribute of IIsWebVirtualDir. i.e.:
<IIsWebServer Location="/LM/W3SVC/67793744" AuthFlags="0" ServerAutoStart="TRUE"
ServerBindings="217.69.47.170:80:app2.dev" ServerComment="app2" />
<IIsWebVirtualDir Location="/LM/W3SVC/67793744/root"
AccessFlags="AccessRead | AccessScript"
AppFriendlyName="Default Application"
AppIsolated="2"
AppRoot="/LM/W3SVC/67793744/Root"
AuthFlags="AuthAnonymous | AuthNTLM"
DirBrowseFlags="DirBrowseShowDate | DirBrowseShowTime | DirBrowseShowSize |
DirBrowseShowExtension | DirBrowseShowLongDate | EnableDefaultDoc"
Path="D:\websites\ssl-test\www\kerboom"
ScriptMaps="...">
But in IIS7 it's stored differently:
<sites>
<site name="Default Web Site" id="1" serverAutoStart="true">
<!-- this is the functional equivalent of the /root app in IIS6 -->
<application path="/">
<virtualDirectory path="/"
physicalPath="%SystemDrive%\inetpub\wwwroot" />
</application>
</site>
<sites>
However, if your code must work with both IIS6 and IIS7 then you can install the IIS6 Management Compatibility components. This will allow you to access IIS7 site properties using traditional IIS6 metabase API's such as ADSI, System.DirectoryServices etc. The compatibility layer will map these properties to the new IIS7 schema for you.
The first part of this article explains how to install this on Vista/Windows7/Windows 2008:
How to install ASP.NET 1.1 with IIS7 on Vista and Windows 2008 - see step #1
Update:
Unfortunately C++ isn't my strong point. However I put together an example in C# using COM Interop to demonstrate using the AppHostWritableAdminManager:
IAppHostWritableAdminManager wam = new AppHostWritableAdminManager();
IAppHostElement sites =
wam.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST");
IAppHostElementCollection sitesCollection = sites.Collection;
long index = FindSiteIndex(sitesCollection, "MySite");
if(index == -1) throw new Exception("Site not found");
IAppHostElement site = sitesCollection[index];
IAppHostElementCollection bindings = site.ChildElements["bindings"].Collection;
for (int i = 0; i < bindings.Count; i++)
{
IAppHostElement binding = bindings[i];
IAppHostProperty protocolProp = binding.GetPropertyByName("protocol");
IAppHostProperty bindingInformationProp =
binding.GetPropertyByName("bindingInformation");
string protocol = protocolProp.Value;
string bindingInformation = bindingInformationProp.Value;
Debug.WriteLine("{0} - {1}", protocol, bindingInformation);
}
static long FindSiteIndex(IAppHostElementCollection sites, string siteName)
{
for (int i = 0; i < sites.Count; i++)
{
IAppHostElement site = sites[i];
Debug.WriteLine(site.Name);
IAppHostProperty prop = site.GetPropertyByName("name");
if(prop.Value == siteName)
{
return i;
}
}
return -1;
}
The code above locates a site named "MySite" in the <sites> collection. It then retrieves the site's <bindings> collection and print's each bindings protocol and bindingInformation attributes.
Your should be able to convert this to C++ fairly easily.
To answer the question in your comment -
For example, the path
system.applicationHost/Sites will get
me to the list of sites. Is there an
absolute way to get to my server
bindings like this (for example by
doing
system.applicationHost/Sites/Default
Web Site/Bindings
When using the AppHostWritableAdminManager there isn't a shortcut to getting directly to the site you want to inspect/modify or it's properties. In the example above, you'll see that I need to loop through the sites collection to find the site I'm interested in. The reason for this is that AppHostWritableAdminManager sees everything as elements and collections of elements. It's a fairly basic API. Even when using the managed Microsoft.Web.Administration API you find that whilst there are some nice properties such as Site.Bindings, these are thinly disguised wrappers around AppHostWritableAdminManager.
In fact if I want to find a site I still have to search the Sites collection either in a for loop or by adding some LINQ sugar if I'm using C#3.5 or later:
using(ServerManager serverManager = new ServerManager())
{
Site x = serverManager.Sites.FirstOrDefault(s => s.Name == "MySite");
}
Site's base class is ConfigurationElement which under the bonnet wraps access to IAppHostElement.
Once you're past some basic shortcut wrapper properties much of what we do in managed code to configure IIS (for example IIS FTP) is elements, attributes and collections of elements.
Update 2:
Please bear in mind I've never written a line of C++ in my life. There's no cleanup of strings or objects:
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <ahadmin.h>
#include <crtdbg.h>
static IAppHostElement*
FindSite(IAppHostElementCollection *pCollection, BSTR bstrSiteName);
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
IAppHostWritableAdminManager *pMgr = NULL;
IAppHostElement *pElem = NULL;
IAppHostElementCollection *pSitesCollection = NULL;
IAppHostElement *pSite = NULL;
IAppHostElement *pBindings = NULL;
IAppHostElement *pBinding = NULL;
IAppHostElementCollection *pBindingsCollection = NULL;
IAppHostChildElementCollection *pChildElements = NULL;
IAppHostProperty *pProtocol = NULL;
IAppHostProperty *pBindingInformation = NULL;
BSTR bstrSectionName = SysAllocString( L"system.applicationHost/sites" );
BSTR bstrConfigCommitPath = SysAllocString( L"MACHINE/WEBROOT/APPHOST" );
BSTR bstrSiteName = SysAllocString( L"MySite" );
BSTR bstrBindingsConst = SysAllocString( L"bindings" );
BSTR bstrBindingProtocol = SysAllocString( L"protocol" );
BSTR bstrBindingInformation = SysAllocString( L"bindingInformation" );
VARIANT vtPropertyName;
VARIANT vtIndex;
HRESULT hr = S_OK;
hr = CoCreateInstance( __uuidof(AppHostWritableAdminManager), NULL,
CLSCTX_INPROC_SERVER, __uuidof(IAppHostWritableAdminManager), (void**) &pMgr);
hr = pMgr->GetAdminSection(bstrSectionName, bstrConfigCommitPath, &pElem);
hr = pElem->get_Collection(&pSitesCollection);
pSite = FindSite(pSitesCollection, bstrSiteName);
hr = pSite->get_ChildElements(&pChildElements);
vtPropertyName.vt = VT_BSTR;
vtPropertyName.bstrVal = bstrBindingsConst;
hr = pChildElements->get_Item(vtPropertyName, &pBindings);
hr = pBindings->get_Collection(&pBindingsCollection);
DWORD bindingsCount;
hr = pBindingsCollection->get_Count(&bindingsCount);
for(int i = 0; i < bindingsCount; i++)
{
vtIndex.lVal = i;
vtIndex.vt = VT_I4;
hr = pBindingsCollection->get_Item(vtIndex, &pBinding);
hr = pBinding->GetPropertyByName(bstrBindingProtocol, &pProtocol);
hr = pBinding->GetPropertyByName(bstrBindingInformation, &pBindingInformation);
BSTR bstrProtocol;
BSTR bstrBindingInformation;
hr = pProtocol->get_StringValue(&bstrProtocol);
hr = pBindingInformation->get_StringValue(&bstrBindingInformation);
_tprintf(_T("Protocol: %s, BindingInfo: %s\n"), bstrProtocol, bstrBindingInformation);
}
CoUninitialize();
return 0;
}
IAppHostElement* FindSite(IAppHostElementCollection *pCollection, BSTR bstrSiteName)
{
DWORD count = -1;
pCollection->get_Count(&count);
BSTR bstrPropName = SysAllocString( L"name");
for(DWORD i = 0; i < count; i++)
{
IAppHostElement *site = NULL;
IAppHostProperty *prop = NULL;
BSTR bstrPropValue;
HRESULT hr = S_OK;
VARIANT vtCount;
VariantInit(&vtCount);
vtCount.lVal = i;
vtCount.vt = VT_I4;
hr = pCollection->get_Item(vtCount, &site);
hr = site->GetPropertyByName(bstrPropName, &prop);
hr = prop->get_StringValue(&bstrPropValue);
if(wcscmp(bstrPropValue, bstrSiteName) == 0)
{
return site;
}
}
return NULL;
}
Related
Been trying this for a while so far with no success, so hoping someone can help out (and that I'm not far off!). I just want to return whether a user is a member of a specific group through LDAP. So far I have the below code;
int authMethod = LDAP_AUTH_SIMPLE;
LDAP* pLdapConnection = NULL;
ULONG version = LDAP_VERSION3;
ULONG getOptSuccess = 0;
ULONG connectSuccess = 0;
INT returnCode = 0;
int retSearch = 0;
LDAPMessage *res;
int num_entries = 0, num_refs = 0;
pLdapConnection = ldap_init((char*)m_Hostname.GetString(), LDAP_PORT);
returnCode = ldap_set_option(pLdapConnection,
LDAP_OPT_PROTOCOL_VERSION,
(void*)&version);
// Connect to the server.
connectSuccess = ldap_connect(pLdapConnection, NULL);
// Bind
returnCode = ldap_bind_s(pLdapConnection, (char*)m_Username.GetString(), (char*)m_Password.GetString(), authMethod);
// Attempt to search for user
retSearch = ldap_search_s(pLdapConnection, "dc=as,dc=local", LDAP_SCOPE_SUBTREE, "(&(sAMAccountName = examplename))", NULL, NULL, &res);
All of this works so far, up until the searching part, for example - I want to search for a user "username" in group "Technical". I've tried things like the below;
retSearch = ldap_search_s(pLdapConnection, "dc=as,dc=local", LDAP_SCOPE_SUBTREE, "(&(sAMAccountName=username)(memberof=CN=Technical))",
nullptr, 0, &pSearchResult);
That does not return anything, so I've tried searching more and the only thing similar I've found is - LDAP Finding Members of a group PHP but it's in PHP and I cannot seem to transfer that over to C++ so far.
Any help in the right direction would be helpful as I cannot work it out. :-)
Your filter should be something like:
(&(objectClass=user)(sAMAccountName=yourUserName)
(memberOf=CN=YourGroup,OU=Users,DC=YourDomain,DC=com))
To include membership due to group nesting:
(&(objectClass=user)(sAMAccountName=yourUserName)
(memberOf:1.2.840.113556.1.4.1941:=cn=YourGroup,ou=Users,dc=YourDomain,dc=com))
The numbers 1.2.840.113556.1.4.1941 are an extended match.
We are trying to write a C++ DLL that would run on VMWare server and would return the client (terminal user) IP Address and name.
I am using WTSQuerySessionInformation to fetch the IP Address. The problem is when I am running from within the company's network, the DLL returns the exact IP Address which maps to an appropriate HostName.
But when I login from home to the company's VPN and try the same, it gives me a different IP which doesn't have any DNS Name.
LPTSTR ppBuffer = NULL;
DWORD pBytesReturned = 0;
PWTS_CLIENT_ADDRESS pWTSCA = NULL;
WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSClientAddress, &ppBuffer, &pBytesReturned);
pWTSCA = (PWTS_CLIENT_ADDRESS)ppBuffer;
String^ addrStr = String::Empty;
for (int i = 2; i < 6; i++)
{
addrStr += Convert::ToString(pWTSCA->Address[i]);
if (i != 5)
addrStr += ".";
}
Is there a way to work around this problem? Am I following the right approach, or there is a different way of doing this?
Edit:
If I use WTSClientName, it return the IP address separate by hyphen (like W-X-Y-Z). Could you please help me understand if I have done anything wrong here? Here is the code:
LPTSTR szClientName = NULL;
DWORD dwSize = 0;
String^ cliName = String::Empty;
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, TSClientName, &szClientName, &dwSize))
{
cliName = gcnew String(szClientName, 0, dwSize);
}
return cliName;
I can't get a specific Outlook (2013) email attachment. I'm working on a little project to learn about MS Office Automation using C++ and I'm testing Outlook automation. In partcular I would like to download a specific email attachment but I can't access to that item. For example, if I have in my inbox an email with 4 attachments I want get the 2nd one.
I tried with this code but the HRESULT returned value from the AutoWrap() method is always not valid:
VARIANT result;
VariantInit(&result);
CComPtr<IDispatch> pAttachments; // email attachments
HRESULT hRes = AutoWrap(DISPATCH_PROPERTYGET, &result, pOfficeItem, L"Attachments", 0);
if (!result.pdispVal || FAILED(hRes)) return EditorError; // EditorError is an Enum
pAttachments = result.pdispVal;
VariantInit(&result);
hRes = AutoWrap(DISPATCH_PROPERTYGET, &result, pAttachments, L"Count", 0);
if (FAILED(hRes)) return EditorError;
int aNumber = result.iVal; // it works, if i have an email with 4 attachments then aNumber is 4
if(aNumber > 0){
VARIANT attachmentIndex;
attachmentIndex.vt = VT_I4;
attachmentIndex.llVal = 0; // I want the 1st attachment
VariantInit(&result);
CComPtr<IDispatch> pAttachmentItem;
hRes = AutoWrap(DISPATCH_PROPERTYGET, &result, pAttachments, L"Item", 1, attachmentIndex);
if (FAILED(hRes)) return EditorError; // here it returns EditorError
}
... DO SOMETHING ...
where AutoWrap() is the method recommended by MS in order to interact with the MS Application (http://www.codeproject.com/Articles/34998/MS-Office-OLE-Automation-Using-C).
Remarks:
The Index property is only valid during the current session and can change as objects are added to and deleted from the collection. The first object in the collection has an Index value of 1.
I have written a c++ MFC DLL that brings up an SDI Application which is a very legacy OLE Server. (I have no choice about using this OLE Server so I have to make it work.)
I am accessing this c++ DLL from C#.
I have everything "working". I can call methods of the DLL, I have implemented C# delegates correctly, etc.
I can call methods of the OLE Server directly in C++ and export these so that my C# application call call them too. This is my "Hello World" for accessing the OLE Server functionality in its entirety from C#.
So far so good.
The next step is to make this C++ DLL as much of a "pass-through" as possible so that C# developers can write business logic around this OLE server. If changes or updates happen from the maker of the OLE Server, we do not want to have to update C++ code, we want to respond to the changes in C#.
So even though I can implement methods in C++ using the imported class from the OLEServer.tlb file and pass these through to C#, we do not want to ultimately do this. I am trying to call methods through the IDispatch interface instead and I am running into difficulties that I can't quite understand.
I am using Visual Studio 2010 for both unmanaged C++ and C#.
VARIANT LaserCat::LaserCatCommand(LPCTSTR* p_pMethodNameAndParamsInReverseOrder, UINT p_Count)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
VARIANT result;
VARIANT* pResult = NULL;
VariantInit(&result);
HRESULT hr = NULL;
DISPID dispid;
const IID IID_ITriad = {0x60EE772D,0xE076,0x4F58,{0xA8,0xB4,0x2F,0x7A,0x29,0xBB,0x02,0x50}};
COleException* pError = NULL;
BOOL HasDispatch = theApp.pTriadView->pTriadItem->Catalog.CreateDispatch(IID_ITriad, pError);
LPDISPATCH iDisp = theApp.pTriadView->pTriadItem->Catalog.m_lpDispatch;
LPOLESTR Names[1] = {(LPOLESTR)L"GetInterfaceVersion"};
hr = iDisp->GetIDsOfNames(IID_NULL, Names, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (hr != S_OK) return result;
DISPPARAMS* pParams = new DISPPARAMS();
short maj = 0;
short min = 0;
short* nMajor = &maj;
short* nMinor = &min;
VARIANTARG Args[2];
VariantInit(&Args[0]);
VariantInit(&Args[1]);
Args[0].piVal = nMinor;
Args[0].vt = VT_BYREF;
Args[1].piVal = nMajor;
Args[1].vt = VT_BYREF;
pParams->rgvarg = Args;
pParams->cNamedArgs = 0;
pParams->cArgs = 2;
pParams->rgdispidNamedArgs = NULL;
EXCEPINFO* pExcept = NULL;
UINT* pArgErrorIndex = NULL;
LPCTSTR Error = NULL;
hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, pParams, pResult, pExcept, pArgErrorIndex);
if (pExcept != NULL || pArgErrorIndex != NULL || hr != S_OK)
{
Error = _T("Error");
return result;
}
result = *pResult;
return result;
}
The following line from above gives me a "Bad variable type" error:
hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, pParams, pResult, pExcept, pArgErrorIndex);
BTW, I am sure that the code can be vastly improved, it has been years since I have written in C++ and this is just a "first kick" at getting this working so no real error handling etc.
I have toyed around with "Args[]" type and value with variations of "Type Mismatch" errors and "Null Reference Pointers"
The function that is imported by the .tlb file looks like this:
short GetInterfaceVersion(short * nMajor, short * nMinor)
{
short result;
static BYTE parms[] = VTS_PI2 VTS_PI2 ;
InvokeHelper(0x178, DISPATCH_METHOD, VT_I2, (void*)&result, parms, nMajor, nMinor);
return result;
}
Oh, although I am passing in the "pMethodNameAndParamsInReverseOrder" as a parameter, I am just hard-coding it to get this one simple method working. Once I have it working with this and a few other methods, I am planning on making this generic to handle any methods implemented by the COM interface via IDispatch.
Any help would be appreciated mostly in getting this working but I would also appreciate any pointers on improving the code, I have mouch to learn in C++
Thank-You!
BTW, If this helps clarify things, theApp.pTriadView->pTriadItem->Catalog is the COM class I am implementing
EDIT:
Thanks to #HansPassant (see first comment) I see what I was missing. Unfortunately I have hit a downstream result of fixing that. The VARIANT "pResult" is coming back empty. I will continue to hunt that down now but any thoughts would be welcome :)
I have a clearQuest Web (running on Linux) and wants to create a sharepoint site when a new record is created (using a perl script).
How can I do it - is there any sharepoint web service that I can use to create a site.
I beleive that I need a perl module for web services, how do I add it to the perl installation of the clearQuest web server ?
Does any one has expirienc with this ?
I have not worked with perl script. But check out http://sharepoint site/_vti_bin/sites.asmx webservice. This webservice can be used manage sites.
I created a custom web service for creating sites in SharePoint (WSS 3), as I couldn't find a way to do it using the existing web services.
The code looks something like this:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class CreateSiteWebService : System.Web.Services.WebService
{
[WebMethod]
public string CreateSite(
string strWebUrl,
string strTitle,
string strDescription,
uint nLCID,
string strWebTemplate,
bool useUniquePermissions,
bool bConvertIfThere
)
{
SPWeb newWeb = null;
SPSite site = SPContext.Current.Site;
newWeb = site.RootWeb.Webs.Add(strWebUrl, strTitle, strDescription, nLCID, strWebTemplate, useUniquePermissions, bConvertIfThere);
newWeb.Navigation.UseShared = true;
newWeb.Update();
//try to get it to appear in quick launch:
SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;
SPNavigationNode menuNode = null;
foreach(SPNavigationNode n in nodes)
{
if (n.Title == "Sites")
{
menuNode = n;
break;
}
}
if (menuNode == null)
{
menuNode = new SPNavigationNode("Sites", site.Url + "/_layouts/viewlsts.aspx?ShowSites=1", false);
nodes.AddAsFirst(menuNode);
}
SPNavigationNode navNode = new SPNavigationNode(strTitle, strWebUrl, false);
menuNode.Children.AddAsLast(navNode);
parent.Update();
parent.Dispose();
site.Dispose();
string url = newWeb.Url;
newWeb.Dispose();
return url;
}
}
Hope that helps.