In .net, I can create a NTAccount using domain and username, and get it's SID.
But I cannot convert the SID back to NTAccount using translate function.
new SecurityIdentifier(stringSid).Translate(typeof(NTAccount)).ToString();
And this two way conversion code has no problem running on Domain Controller.
Maybe some configuration wrong?
SecurityIdentifier.Translate() method works only on domain accounts so perhaps your computer not attached to domain. To resolve local SIDs into account name you can use Win32 API function LookupAccountSid() look here for example.
Instead of using the SecurityIdentifier, you can use an easier and more general use of DirectoryServices in .NET.
In codeproject, there is a nice sample of this:
http://www.codeproject.com/KB/cs/getusersid.aspx
The code is:
private string GetSid(string strLogin)
{
string str = "";
// Parse the string to check if domain name is present.
int idx = strLogin.IndexOf('\\');
if (idx == -1)
{
idx = strLogin.IndexOf('#');
}
string strDomain;
string strName;
if (idx != -1)
{
strDomain = strLogin.Substring(0, idx);
strName = strLogin.Substring(idx+1);
}
else
{
strDomain = Environment.MachineName;
strName = strLogin;
}
DirectoryEntry obDirEntry = null;
try
{
Int64 iBigVal = 5;
Byte[] bigArr = BitConverter.GetBytes(iBigVal);
obDirEntry = new DirectoryEntry("WinNT://" +
strDomain + "/" + strName);
System.DirectoryServices.PropertyCollection
coll = obDirEntry.Properties;
object obVal = coll["objectSid"].Value;
if (null != obVal)
{
str = this.ConvertByteToStringSid((Byte[])obVal);
}
}
catch (Exception ex)
{
str = "";
Trace.Write(ex.Message);
}
return str;
}
private string ConvertByteToStringSid(Byte[] sidBytes)
{
StringBuilder strSid = new StringBuilder();
strSid.Append("S-");
try
{
// Add SID revision.
strSid.Append(sidBytes[0].ToString());
// Next six bytes are SID authority value.
if (sidBytes[6] != 0 || sidBytes[5] != 0)
{
string strAuth = String.Format
("0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}",
(Int16)sidBytes[1],
(Int16)sidBytes[2],
(Int16)sidBytes[3],
(Int16)sidBytes[4],
(Int16)sidBytes[5],
(Int16)sidBytes[6]);
strSid.Append("-");
strSid.Append(strAuth);
}
else
{
Int64 iVal = (Int32)(sidBytes[1]) +
(Int32)(sidBytes[2] << 8) +
(Int32)(sidBytes[3] << 16) +
(Int32)(sidBytes[4] << 24);
strSid.Append("-");
strSid.Append(iVal.ToString());
}
// Get sub authority count...
int iSubCount = Convert.ToInt32(sidBytes[7]);
int idxAuth = 0;
for (int i = 0; i < iSubCount; i++)
{
idxAuth = 8 + i * 4;
UInt32 iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth);
strSid.Append("-");
strSid.Append(iSubAuth.ToString());
}
}
catch (Exception ex)
{
Trace.Warn(ex.Message);
return "";
}
return strSid.ToString();
}
There is also a conversion from SID bytes to String in the article.
Related
I have the following code and it works fine when I loop through one by one using foreach, but when I change it to use Parallel.ForEach I'm getting errors. Trying to figure out how I can correct this. FYI the dParms list contain unique id's.
The error I'm getting is
Error writing to path..... ErrorMessage:Item has already been added.
Key in dictionary: 'IIS_WasUrlRewritten' Key being added:
'IIS_WasUrlRewritten',
private void GeneratePages(RequestStatus response, string localDir, List<Parameters> dParms, int total, DateTime generatedTime)
{
int current = 0;
Parallel.ForEach(dParms, curJob =>
{
try
{
DownloadPage(localDir, curJob, generatedTime);
}
catch (Exception ex)
{
response.Status = false;
response.Message = ex.Message;
}
finally
{
Interlocked.Increment(ref current);
if (current % 10 == 0)
//to do: Send Progress to UI
}
//});
}
private string DownloadPage(string localDir, Parameters p, DateTime generatedTime)
{
string strExtension = "html";
string url = string.Empty;
url = this.Url.Action("MyAction", "Home", new { area = "", #id = p.MyId, #generatedTime = generatedTime }, this.Request.Url.Scheme);
var document = new HtmlWeb().Load(url);
string strFileName = url.Substring(url.LastIndexOf("/") + 1);
strFileName = strFileName.Substring(0, strFileName.IndexOf("generatedTime") - 1);
string strDiskFileName = strFileName.Replace(".aspx?", "");
strDiskFileName = strDiskFileName.Replace("?", "");
strDiskFileName = strDiskFileName.Replace(".aspx", "");
strDiskFileName = strDiskFileName.Replace("&", "");
strDiskFileName = strDiskFileName.Replace("=", "");
strDiskFileName = strDiskFileName.Replace("%20", "");
strDiskFileName += "." + strExtension;
document.Save(localDir + strDiskFileName);
return url;
}
I am trying write a function that takes a single IP address as a parameter and queries that machine on my local network for it's MAC address.
I have seen many examples that get the local machine's own MAC address, however none (I've found) that seem to query a local network machine for it.
I know such a task is achievable as this Wake on LAN scanner software scans the local IP range and returns MAC address/Hostname of all on machines.
Can anyone tell me where I'd get started trying to write a function to achieve this in C#? Any help would be appreciated. Thanks
EDIT:
As per Marco Mp's comment below, have used ARP tables.
arp class
public string GetMacAddress(string ipAddress)
{
string macAddress = string.Empty;
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
pProcess.StartInfo.FileName = "arp";
pProcess.StartInfo.Arguments = "-a " + ipAddress;
pProcess.StartInfo.UseShellExecute = false;
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.StartInfo.CreateNoWindow = true;
pProcess.Start();
string strOutput = pProcess.StandardOutput.ReadToEnd();
string[] substrings = strOutput.Split('-');
if (substrings.Length >= 8)
{
macAddress = substrings[3].Substring(Math.Max(0, substrings[3].Length - 2))
+ "-" + substrings[4] + "-" + substrings[5] + "-" + substrings[6]
+ "-" + substrings[7] + "-"
+ substrings[8].Substring(0, 2);
return macAddress;
}
else
{
return "not found";
}
}
Very late edit:
In open souce project iSpy (https://github.com/ispysoftware/iSpy) they use this code, which is a little nicer
public static void RefreshARP()
{
_arpList = new Dictionary<string, string>();
_arpList.Clear();
try
{
var arpStream = ExecuteCommandLine("arp", "-a");
// Consume first three lines
for (int i = 0; i < 3; i++)
{
arpStream.ReadLine();
}
// Read entries
while (!arpStream.EndOfStream)
{
var line = arpStream.ReadLine();
if (line != null)
{
line = line.Trim();
while (line.Contains(" "))
{
line = line.Replace(" ", " ");
}
var parts = line.Trim().Split(' ');
if (parts.Length == 3)
{
string ip = parts[0];
string mac = parts[1];
if (!_arpList.ContainsKey(ip))
_arpList.Add(ip, mac);
}
}
}
}
catch (Exception ex)
{
Logger.LogExceptionToFile(ex, "ARP Table");
}
if (_arpList.Count > 0)
{
foreach (var nd in List)
{
string mac;
ARPList.TryGetValue(nd.IPAddress.ToString(), out mac);
nd.MAC = mac;
}
}
}
https://github.com/ispysoftware/iSpy/blob/master/Server/NetworkDeviceList.cs
Update 2 even more late, but I think is best because it uses regex that checks better for exact matches.
public string getMacByIp(string ip)
{
var macIpPairs = GetAllMacAddressesAndIppairs();
int index = macIpPairs.FindIndex(x => x.IpAddress == ip);
if (index >= 0)
{
return macIpPairs[index].MacAddress.ToUpper();
}
else
{
return null;
}
}
public List<MacIpPair> GetAllMacAddressesAndIppairs()
{
List<MacIpPair> mip = new List<MacIpPair>();
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
pProcess.StartInfo.FileName = "arp";
pProcess.StartInfo.Arguments = "-a ";
pProcess.StartInfo.UseShellExecute = false;
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.StartInfo.CreateNoWindow = true;
pProcess.Start();
string cmdOutput = pProcess.StandardOutput.ReadToEnd();
string pattern = #"(?<ip>([0-9]{1,3}\.?){4})\s*(?<mac>([a-f0-9]{2}-?){6})";
foreach (Match m in Regex.Matches(cmdOutput, pattern, RegexOptions.IgnoreCase))
{
mip.Add(new MacIpPair()
{
MacAddress = m.Groups["mac"].Value,
IpAddress = m.Groups["ip"].Value
});
}
return mip;
}
public struct MacIpPair
{
public string MacAddress;
public string IpAddress;
}
using System.Net;
using System.Runtime.InteropServices;
[DllImport("iphlpapi.dll", ExactSpelling = true)]
public static extern int SendARP(int DestIP, int SrcIP, [Out] byte[] pMacAddr, ref int PhyAddrLen);
try
{
IPAddress hostIPAddress = IPAddress.Parse("XXX.XXX.XXX.XX");
byte[] ab = new byte[6];
int len = ab.Length,
r = SendARP((int)hostIPAddress.Address, 0, ab, ref len);
Console.WriteLine(BitConverter.ToString(ab, 0, 6));
}
catch (Exception ex) { }
or with PC Name
try
{
Tempaddr = System.Net.Dns.GetHostEntry("DESKTOP-xxxxxx");
}
catch (Exception ex) { }
byte[] ab = new byte[6];
int len = ab.Length, r = SendARP((int)Tempaddr.AddressList[1].Address, 0, ab, ref len);
Console.WriteLine(BitConverter.ToString(ab, 0, 6));
Just a better performing version of the accepted method.
public string GetMacByIp( string ip )
{
var pairs = this.GetMacIpPairs();
foreach( var pair in pairs )
{
if( pair.IpAddress == ip )
return pair.MacAddress;
}
throw new Exception( $"Can't retrieve mac address from ip: {ip}" );
}
public IEnumerable<MacIpPair> GetMacIpPairs()
{
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
pProcess.StartInfo.FileName = "arp";
pProcess.StartInfo.Arguments = "-a ";
pProcess.StartInfo.UseShellExecute = false;
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.StartInfo.CreateNoWindow = true;
pProcess.Start();
string cmdOutput = pProcess.StandardOutput.ReadToEnd();
string pattern = #"(?<ip>([0-9]{1,3}\.?){4})\s*(?<mac>([a-f0-9]{2}-?){6})";
foreach( Match m in Regex.Matches( cmdOutput, pattern, RegexOptions.IgnoreCase ) )
{
yield return new MacIpPair()
{
MacAddress = m.Groups[ "mac" ].Value,
IpAddress = m.Groups[ "ip" ].Value
};
}
}
public struct MacIpPair
{
public string MacAddress;
public string IpAddress;
}
With SharpPCap, It will work on Windows & Linux
public string getMacAdress(string ip){
LibPcapLiveDeviceList devices = LibPcapLiveDeviceList.Instance;//list all your network cards
ARP arp = new ARP(devices[0]);//select the first network card by default
IPAddress ip = IPAddress.Parse(ip);
PhysicalAddress macAdress = arp.Resolve(ip);
return macAdress.ToString();
}
As per Marco Mp's comment above, have used ARP tables. arp class
I use this function to find any installed application.
But the parameter "InstallLocation" doesn't work at all.
Any clue?
Thanks!!
void FindApplication(string appName)
{
StringBuilder sbProductCode = new StringBuilder(39);
int iIdx = 0;
while (0 == MsiEnumProducts(iIdx++, sbProductCode))
{
Int32 productNameLen = 512;
StringBuilder sbProductName = new StringBuilder(productNameLen);
MsiGetProductInfo(sbProductCode.ToString(), "ProductName", sbProductName, ref productNameLen);
if (sbProductName.ToString().Contains(appName))
{
Int32 installDirLen = 2048;
StringBuilder sbInstallDir = new StringBuilder(installDirLen);
MsiGetProductInfo(sbProductCode.ToString(),"InstallLocation", sbInstallDir, ref installDirLen);
string result = string.Format("ProductName {0}: {1}", sbProductName, sbInstallDir);
}
}
}
I've visited the following links, and they don't appear to be outdated:
MsiGetProductInfo function
MsiGetProductInfoEx function
The only keys I see that can be used are these:
ARPINSTALLLOCATION
INSTALLDIR
INSTALLPROPERTY_INSTALLLOCATION
INSTALLLOCATION
I should point out that it appears MsiGetProductInfoEx (second link) should be used to gather information on published/installed products added by another user; and requires administrative privledges.
I found other solution and it works fine.
string FindPathByInstalledAppEXEName(string appEXEName)
{
string path = string.Empty;
try
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\Installer\Assemblies");
string regfilepath = string.Empty;
if (key != null) // Make sure there are Assemblies
{
foreach (string Keyname in key.GetSubKeyNames())
{
if (Keyname.IndexOf(appEXEName) > 0)
{
regfilepath = Keyname;
break;
}
}
}
if (!string.IsNullOrEmpty(regfilepath))
{
string fullpath = "";
for (int a = 0; a < regfilepath.Length; a++)
{
if (regfilepath.IndexOf("|", a, 1) > 0)
fullpath += "\\";
else
fullpath += regfilepath.Substring(a, 1);
}
path = fullpath.Substring(0, fullpath.LastIndexOf("\\") + 1);
}
}
catch // (Exception ex)
{
}
return path;
}
I need to create a new website in iis 6.0 (Windows server 2003 r2 sp2) and create a new virtual directory with .net framework 4.0 and assign it to created website.
here is my code :
public static int CreateWebSite(string webSiteName, string PhysicalPath, string PortNumber, string HostHeader, string DefaultDoc, string appPoolName, out string errorMessage)
{
errorMessage = string.Empty;
try
{
DirectoryEntry root = new DirectoryEntry("IIS://localhost/W3SVC");
// Find unused ID value for new web site
int siteID = 1;
foreach (DirectoryEntry e in root.Children)
{
if (e.SchemaClassName == "IIsWebServer")
{
int ID = Convert.ToInt32(e.Name);
if (ID >= siteID)
{
siteID = ID + 1;
}
}
}
DirectoryEntry site = (DirectoryEntry)root.Invoke("Create", "IIsWebServer", siteID);
site.Invoke("Put", "ServerComment", webSiteName);
site.Invoke("Put", "KeyType", "IIsWebServer");
site.Invoke("Put", "ServerBindings", ":" + PortNumber + ":" + HostHeader);
site.Invoke("Put", "ServerState", 2);
site.Invoke("Put", "FrontPageWeb", 1);
site.Invoke("Put", "DefaultDoc", DefaultDoc);
site.Invoke("Put", "ServerAutoStart", 1);
site.Invoke("Put", "ServerSize", 1);
site.Invoke("SetInfo");
DirectoryEntry siteVDir = site.Children.Add("Root", "IISWebVirtualDir");
if (appPoolName != "")
{
object[] param = { 0, appPoolName, true };
siteVDir.Invoke("AppCreate3", param);
}
siteVDir.Properties["AppIsolated"][0] = 2;
siteVDir.Properties["Path"][0] = PhysicalPath;
siteVDir.Properties["AccessFlags"][0] = 513;
siteVDir.Properties["AspEnableParentPaths"][0] = true;
siteVDir.Properties["AppFriendlyName"][0] = webSiteName;
siteVDir.Properties["FrontPageWeb"][0] = 1;
siteVDir.Properties["AppRoot"][0] = "LM/W3SVC/" + siteID + "/Root";
siteVDir.Properties["AppFriendlyName"][0] = "Root";
siteVDir.Properties["AspSessionTimeout"][0] = "60";
siteVDir.Properties["AuthFlags"].Value = 4;//integrity windows Authentication checked
siteVDir.Properties["AuthAnonymous"][0] = true;//Anonymouse uncheck
siteVDir.Properties["HttpErrors"].Add("401,1,FILE," + PhysicalPath + "/Lib/CustomError/SSOLoginError.htm");
siteVDir.Properties["HttpErrors"].Add("401,2,FILE," + PhysicalPath + "/Lib/CustomError/SSOLoginError.htm");
siteVDir.Properties["HttpErrors"].Add("401,3,FILE," + PhysicalPath + "/Lib/CustomError/SSOLoginError.htm");
siteVDir.Properties["HttpErrors"].Add("401,4,FILE," + PhysicalPath + "/Lib/CustomError/SSOLoginError.htm");
siteVDir.Properties["HttpErrors"].Add("401,5,FILE," + PhysicalPath + "/Lib/CustomError/SSOLoginError.htm");
siteVDir.Properties["HttpErrors"].Add("401,7,FILE," + PhysicalPath + "/Lib/CustomError/SSOLoginError.htm");
//For SSO, Set special settings for WinLogin.aspx page -- This has beed added after version 8.1.1001
DirectoryEntry deLoginDir;
deLoginDir = siteVDir.Children.Add("WinLogin.aspx", siteVDir.SchemaClassName);
deLoginDir.Properties["AuthAnonymous"][0] = false;//Anonymouse uncheck
deLoginDir.Properties["AuthFlags"].Value = 4;//integrity windows Authentication checked
deLoginDir.CommitChanges();
////////////////////////////////////////////
siteVDir.CommitChanges();
siteVDir.Invoke("AppDelete");
siteVDir.Invoke("AppCreate", true);
siteVDir.Invoke("AppEnable");
site.CommitChanges();
#region AssignApplicationPool
DirectoryEntry vDir = new DirectoryEntry("IIS://localhost/W3SVC/" + siteID.ToString() + "/Root");
string className = vDir.SchemaClassName.ToString();
if (className.EndsWith("VirtualDir"))
{
object[] param = { 0, appPoolName, true };
vDir.Invoke("AppCreate3", param);
vDir.Properties["AppIsolated"][0] = "2";
vDir.CommitChanges();
}
else
{
return -1;
}
if (Environment.OSVersion.Version.Major < 6)
{
try
{
const string aspNetV1 = "1.0.3705";
const string aspNetV11 = "1.1.4322";
const string aspNetV2 = "2.0.50727";
const string aspNetV4 = "4.0.30319";
const string targetAspNetVersion = aspNetV4;
//loop through the script maps
for (var i = 0; i < siteVDir.Properties["ScriptMaps"].Count; i++)
{
//replace the versions if they exists
siteVDir.Properties["ScriptMaps"][i] =
siteVDir.Properties["ScriptMaps"][i].ToString().Replace(aspNetV1, targetAspNetVersion);
siteVDir.Properties["ScriptMaps"][i] =
siteVDir.Properties["ScriptMaps"][i].ToString().Replace(aspNetV11, targetAspNetVersion);
siteVDir.Properties["ScriptMaps"][i] =
siteVDir.Properties["ScriptMaps"][i].ToString().Replace(aspNetV2, targetAspNetVersion);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
errorMessage = ex.Message + Environment.NewLine + ex.StackTrace;
}
}
else
{
string appPoolPath = #"IIS://localhost/W3SVC/AppPools/" + appPoolName;
try
{
var appPoolEntry = new DirectoryEntry(appPoolPath);
appPoolEntry.Properties["managedRuntimeVersion"].Value = "v4.0";
appPoolEntry.Invoke("SetInfo", null);
appPoolEntry.CommitChanges();
appPoolEntry.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
errorMessage = ex.Message + Environment.NewLine + ex.StackTrace;
}
siteVDir.CommitChanges();
siteVDir.Close();
}
#endregion
return siteID;
}
catch
{
return -1;
}
}
This code should work fine in IIS 6.0 (Windows 2003) and IIS 7.5+ (Windows 7 and Windows 2008).
in windows 7 and 2008 every thing looks fine! but in windows 2003 I have some problems:
Asp.net version of website does not change to v4
Application Pool does not seems correct and its node in inetmgr only have one node with name Root not the name of website
in the properties window of website -> home directory tab, application name is empty!
what is wrong with my code?
thanks in advance
DirectoryEntry sited = new DirectoryEntry(string.Format("IIS://localhost/w3svc/{0}/Root", websiteID.ToString()));
sited.Properties["AccessRead"].Add(true);
PropertyValueCollection testScriptMap = sited.Properties["ScriptMaps"];
object[] allValues = (object[])testScriptMap.Value;
object[] newValues = new object[allValues.Length];
string oldVersion = "v1.1.4322";
string newVersion = "v2.0.50727";
//... etc to 4.0.30319 .....
for (int i = 0; i < allValues.Length; i++)
{
if (allValues[i] is string)
{
string temp = allValues[i] as string;
if (temp.Contains(oldVersion))
{
newValues[i] = temp.Replace(oldVersion, newVersion);
}
else
{
newValues[i] = allValues[i];
}
}
else
{
newValues[i] = allValues[i];
}
}
testScriptMap.Value = newValues;
sited.CommitChanges();
Working in C# with the EWS Managed API, we're having trouble efficiently retrieving the images stored as inline attachments.
The endpoint is to show an email with inline images as a fully formed html page in a panel. The code we currently us:
string sHTMLCOntent = item.Body;
FileAttachment[] attachments = null;
if (item.Attachments.Count != 0)
{
attachments = new FileAttachment[item.Attachments.Count];
for (int i = 0; i < item.Attachments.Count; i++)
{
string sType = item.Attachments[i].ContentType.ToLower();
if (sType.Contains("image"))
{
attachments[i] = (FileAttachment)item.Attachments[i];
string sID = attachments[i].ContentId;
sType = sType.Replace("image/", "");
string sFilename = sID + "." + sType;
string sPathPlusFilename = Directory.GetCurrentDirectory() + "\\" + sFilename;
attachments[i].Load(sFilename);
string oldString = "cid:" + sID;
sHTMLCOntent = sHTMLCOntent.Replace(oldString, sPathPlusFilename);
}
}
}
(sourced: http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/ad10283a-ea04-4b15-b20a-40cbd9c95b57)
.. this is not very efficient though and is slowing down the responsiveness of our web app. Does anyone have a better solution for this problem? We are using Exchange 2007 SP1, so the IsInline property wont work as its Exchange 2010 only.
I build an index of your "cid:"s first:
private const string CidPattern = "cid:";
private static HashSet<int> BuildCidIndex(string html)
{
var index = new HashSet<int>();
var pos = html.IndexOf(CidPattern, 0);
while (pos > 0)
{
var start = pos + CidPattern.Length;
index.Add(start);
pos = html.IndexOf(CidPattern, start);
}
return index;
}
Then you need a replace function that replaces the cids based on your index
private static void AdjustIndex(HashSet<int> index, int oldPos, int byHowMuch)
{
var oldIndex = new List<int>(index);
index.Clear();
foreach (var pos in oldIndex)
{
if (pos < oldPos)
index.Add(pos);
else
index.Add(pos + byHowMuch);
}
}
private static bool ReplaceCid(HashSet<int> index, ref string html, string cid, string path)
{
var posToRemove = -1;
foreach (var pos in index)
{
if (pos + cid.Length < html.Length && html.Substring(pos, cid.Length) == cid)
{
var sb = new StringBuilder();
sb.Append(html.Substring(0, pos-CidPattern.Length));
sb.Append(path);
sb.Append(html.Substring(pos + cid.Length));
html = sb.ToString();
posToRemove = pos;
break;
}
}
if (posToRemove < 0)
return false;
index.Remove(posToRemove);
AdjustIndex(index, posToRemove, path.Length - (CidPattern.Length + cid.Length));
return true;
}
so now, you can check your attachments
FileAttachment[] attachments = null;
var index = BuildCidIndex(sHTMLCOntent);
if (index.Count > 0 && item.Attachments.Count > 0)
{
var basePath = Directory.GetCurrentDirectory();
attachments = new FileAttachment[item.Attachments.Count];
for (var i = 0; i < item.Attachments.Count; ++i)
{
var type = item.Attachments[i].ContentType.ToLower();
if (!type.StartsWith("image/")) continue;
type = type.Replace("image/", "");
var attachment = (FileAttachment)item.Attachments[i];
var cid = attachment.ContentId;
var filename = cid + "." + type;
var path = Path.Combine(basePath, filename);
if(ReplaceCid(index, ref sHTMLCOntent, cid, path))
{
// only load images when they have been found
attachment.Load(path);
attachments[i] = attachment;
}
}
}
Additional to that: instead of calling attachment.Load right away, and pass the path to the image directly, you could link to another script, where you pass the cid as a parameter and then check back with the exchange for that image; then the process of loading the image from exchange does not block the html cid replacement and could lead to loading the page faster, since the html can send to the browser sooner.
PS: Code is not tested, just so you get the idea!
EDIT
Added the missing AdjustIndex function.
EDIT 2
Fixed small bug in AdjustIndex