what you _don't_ see is what you get: digging into the details of .net reflector's forced auto-update "feature".
one little annoyance - and suspicion - in the .net development world is the behavior of .net reflector when it comes to updating itself. there's no way around it. and if you're not careful, the thing will even delete itself in some circumstances. that behavior is suspiciously close to being a solid example of stallman's "treacherous computing" criticism.
phone home
using the current version, go to "Help -> Check for Updates". fiddler shows that Reflector connects to location http://reflector.red-gate.com/reflector.version, sending the request:
GET /Reflector.version HTTP/1.1
Cache-Control: no-cache, no-store, max-age=0<
Host: reflector.red-gate.com
Proxy-Connection: Keep-Alive
and getting the response:
HTTP/1.1 200 OK
Content-Length: 63
Content-Type: text/plain
Last-Modified: Fri, 17 Oct 2008 09:34:07 GMT
Accept-Ranges: bytes
ETag: "a4fb7b803b30c91:5fd"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Date: Fri, 20 Mar 2009 20:08:16 GMT
5.1.4.0
4.0.0.0
http://downloads.red-gate.com/reflector.zip
setting up a fiddler auto-response for the request like this seems to work. better would be to set up a dns entry somewhere rerouting that specific url to a (local) http proxy that does the same. could even do this so that reflector versions across an organization or project pull from the same update cache - under the control of the consumer.
ok, so that satisfies a bit of paranoia regarding what the call-home exchange may have been. why isn't it documented more transparently ? and still, that's not really acceptable behavior, for any reason.
delete me
next, need to install & run an old version to see what attempting to fire that up does. i've got one hanging around from a couple of years ago, dated 2006.12.10, v4.2.51.0.
trying to fire it up results in:

select "no", and i get this:

reflector.exe was marked read only, so it remained.
running it again and responding "yes" to the update question, it reaches for http://www.aisto.com/roeder/dotnet/Reflector.version. if it can't find that for some reason (say, from being offline), it displays an error message:
and proceeds to (attempt to) delete the executable as before.
running again, responding "yes", and letting it find the (faked) response as above, it reaches for the download reference in the response. failing here, it also attempts to delete the executable.
now, before going further, if i change the first number version in the response to be the same as the one i'm trying to run, the update dialog is still presented, and returns immediately with

but - after closing this dialog, the deletion attempt is still made. yeah, it's time-bombed from the inside alright. i even tried feeding it the old version zip as the download reference in the faked response - that just loops the deletion / update process; good if you're in the mood for some strange sort of self-abuse.
the only way around that deletion that anyone seems to have found is to change the system date before starting the app. i haven't tried, but it is probably possible to set up a cmd file to do the date change in a way reflector will recognize, launch & change back.
since reflector's internals are well protected, it seems the most practical approach is to hope redgate listens to the folks on its forums about this.
just ugly.
first refactoring of my twitter to blog post aggregator (code).
it usually takes me three rounds of coding to get something somewhat satisfactory - the original "just get it to work" phase, and two refactorings. the first refactoring follows, with some notes in the comments for the next round:
- fractalnavel.CS.PostTwitter.cs
using System;
using System.Xml;
using CommunityServer.Blogs.Components;
using CookComputing.XmlRpc;
using fractalnavel.CS.components;
namespace fractalnavel.CS
{
class CMain
{
[STAThread]
static void Main(string[] args)
{
XmlDocument xmlSettings = new XmlDocument();
xmlSettings.Load( args[0] );
GetAndPost gap = new GetAndPost(
xmlSettings.DocumentElement,
(XmlRpcProxyGen.Create<IMetaWebLogNewPost>()).newPost
);
if ( gap.Get() )
gap.Post();
else if ( gap.PostNone )
gap.Post( "nothin'" );
}
}
[XmlRpcUrl("*** wherever it was put *** /metablog.ashx")]
public interface IMetaWebLogNewPost
{
[XmlRpcMethod("metaWeblog.newPost")]
string newPost(
string blogid,
string username,
string password,
MetaWeblog.Post post,
bool publish
);
}
}
- fractalnavel.CS.components.cs
using System;
using System.Xml;
using System.Xml.Serialization;
using CommunityServer.Blogs.Components;
using fractalnavel.components;
namespace fractalnavel.CS.components
{
public class GetAndPost
{
private MakeNewPost _doPost;
private Settings _settings;
private XmlDocument _xmldocAll = new XmlDocument();
private string _strPostBody = string.Empty;
public delegate string MakeNewPost(
string blogid,
string username,
string password,
MetaWeblog.Post post,
bool publish
);
public GetAndPost( XmlNode nodSettings )
{
GetAndPost( nodSettings, (new MetaWeblog()).newPost );
}
public GetAndPost( XmlNode nodSettings, MakeNewPost DoPost )
{
_doPost = DoPost;
_settings = (Settings) XmlUtils.Deserialize <Settings> ( nodSettings );
}
public bool PostNone {
get{ return _settings.bPostNone; }
}
public string PostBody
{
set {_strPostBody = value;}
get
{
if ( _strPostBody == string.Empty )
_strPostBody = XmlUtils.Transform( _xmldocAll, _settings.hrefXsl );
return _strPostBody;
}
}
public bool Get()
{
string href = _settings.hrefGet + "?count=200&since=" + ((DateTime.UtcNow).AddHours( - _settings.hours )).ToString("r");
_xmldocAll.LoadXml( XmlUtils.GetTextFromHref( href, _settings.useridGet, _settings.pwdGet ) );
return (_xmldocAll.SelectNodes("//status")).Count > 0;
}
public string Post()
{
return Post( this.PostBody );
}
public string Post( string strPostBody )
{
bool bPublish = true;
DateTime dtNow = DateTime.Now;
MetaWeblog.Post post = new MetaWeblog.Post();
post.title = _settings.titleBlog + " " + (dtNow).ToString("yyyy.MM.dd");
post.dateCreated = dtNow;
post.description = strPostBody;
return _doPost( _settings.idBlog, _settings.useridBlog, _settings.pwdBlog, post, bPublish);
}
[Serializable()]
public class Settings
{
private string _hrefGet;
private string _useridGet;
private string _pwdGet;
private string _hrefXsl;
private string _idBlog;
private string _useridBlog;
private string _pwdBlog;
private string _titleBlog;
private int _intHours;
private bool _bPostNone;
public Settings() {}
[XmlAttribute("hrefGet")]
public string hrefGet{get {return this._hrefGet;} set {this._hrefGet = value;}}
[XmlAttribute("useridGet")]
public string useridGet{get {return this._useridGet;} set {this._useridGet = value;}}
[XmlAttribute("pwdGet")]
public string pwdGet{get {return this._pwdGet;} set {this._pwdGet = value;}}
[XmlAttribute("hrefXsl")]
public string hrefXsl{get {return this._hrefXsl;} set {this._hrefXsl = value;}}
[XmlAttribute("idBlog")]
public string idBlog{get {return this._idBlog;} set {this._idBlog = value;}}
[XmlAttribute("useridBlog")]
public string useridBlog{get {return this._useridBlog;} set {this._useridBlog = value;}}
[XmlAttribute("pwdBlog")]
public string pwdBlog{get {return this._pwdBlog;} set {this._pwdBlog = value;}}
[XmlAttribute("titleBlog")]
public string titleBlog{get {return this._titleBlog;} set {this._titleBlog = value;}}
[XmlAttribute("hours")]
public int hours{get {return this._intHours;} set {this._intHours = value;}}
[XmlAttribute("bPostNone")]
public bool bPostNone{get {return this._bPostNone;} set {this._bPostNone = value;}}
}
}
}
- fractalnavel.components.cs
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Xsl;
namespace fractalnavel.components
{
public class XmlUtils
{
public static string GetTextFromHref(string url, string user, string password)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Credentials = new NetworkCredential(user, password);
WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseString = reader.ReadToEnd();
reader.Close();
return responseString;
}
public static string Transform( string strXmlHref, string strXmlUserId, string strXmlPassword, string strXslHref )
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml( GetTextFromHref( strXmlHref, strXmlUserId, strXmlPassword ) );
return Transform( xmlDoc, strXslHref );
}
public static string Transform( XmlDocument xmlDoc, string strXslHref )
{
XslTransform xsl = new XslTransform();
xsl.Load( strXslHref, new XmlUrlResolver() );
StringBuilder sbResult = new StringBuilder();
StringWriter swResult = new StringWriter( sbResult );
xsl.Transform( xmlDoc, null, swResult, null );
return sbResult.ToString();
}
public static Object Deserialize <T> (XmlNode node)
{
XmlSerializer ser = new XmlSerializer( typeof(T) );
return ser.Deserialize(new XmlNodeReader(node));
}
}
}
- Settings.xml
<?xml version="1.0" encoding="utf-8" ?>
<>
<Settings
hrefGet="http://twitter.com/statuses/user_timeline.xml"
useridGet="***"
pwdGet="***"
hrefXsl="*** wherever it was put *** /GetAndPost.xsl"
idBlog="*** destination weblog name ***"
useridBlog="***"
pwdBlog="***"
titleBlog="a day in a life"
hours="24"
bPostNone="false"
/>
- GetAndPost.xsl
<?xml version="1.0" ?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:user="http://blogs.no-ip.org/fractalnavel"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="xsl user msxsl"
>
<xsl:output
method='html'
omit-xml-declaration="yes"
version="1.0"
encoding="UTF-8"
indent="yes"
cdata-section-elements=""
/>
<>
<xsl:template match="/statuses">
<div class="divLTAll">
<div class="divLTHead">
via @<a href="http://www.twitter.com/*** who you are ***" target="_blank"
title="twitter!">*** who you are ***</a>: in the last twenty-four hours:
</div>
<div class="divLTBody">
<ul>
<xsl:apply-templates select="status" >
<xsl:sort select="position()" order="descending" data-type="number"/>
</xsl:apply-templates>
</ul>
</div>
<div class="divLTFoot">
(pulled direct from twitter via custom job)
</div>
</div>
</xsl:template>
<xsl:template match="status">
<li><span class="spnLTItemDate"><xsl:value-of select="user:fnNormalizeDate(string(created_at))" /></span>:
<xsl:value-of select="user:fnSetLinks(string(text))" disable-output-escaping="yes" /></li>
</xsl:template>
<msxsl:script language="JScript" implements-prefix="user">
//<![CDATA[
function fnSetLinks( strText )
{
// embedded hrefs
strText = strText.replace( /(http:\/\/\S*)/g, "<a href=\"$1\" class=\"aLTLink\" target=\"_blank\">$1</a>" );
// twitter ids
strText = strText.replace( /@(\S*)/g, "@<a href=\"http://www.twitter.com/$1\" class=\"aLTUser\" target=\"_blank\">$1</a>" );
// hash tags
strText = strText.replace( /#(\S*)/g, "<a href=\"http://search.twitter.com/search?q=%23$1\" class=\"aLTHash\" target=\"_blank\">$1</a>" );
return strText;
}
function fnNormalizeDate(strDate) {
try {
// "Mon Mar 16 18:16:59 +0000 2009" to "2009.03.16 18:16":
var rxMatch = /.{3} (.{3}) (\d{2}) (\d{2}:\d{2}):\d{2} \+\d{4} (\d{4})/;
strDate = strDate.replace( rxMatch ,
function(strMatch, strMonth, strDay, strTime, strYear)
{
var dat = new Date( strMonth + " " + strDay + " " + strYear + " " + strTime );
// months number is zero based ?!? wtf
strMonth = (dat.getUTCMonth()+1).toString().replace( /^(\d)$/, "0$1" );
return strYear + "." + strMonth + "." + strDay + " " + strTime;
});
} catch(e) {
strDate = e.message;
}
return strDate ;
}
//]]>
</msxsl:script>
</xsl:stylesheet>
i also need to reorganize the naming a bit. i'll reserve commentary until the last revisions are complete.