第一次遇到开心的时候,他就问我有没有装过Team Foundation Server。我说我只做Visual Studio,不碰Server,他教育我,Team Foundation Server就是Whidbey的一部分……
今天终于有business requirement要装TFS了,同事和我开玩笑说,全中国现在只有几个人装成,弄得我兴趣大增……
刚才装好了,简单看了一看,测试了一下,operational,但还不怎么会用……
要求是蛮夸张的,两台Windows 2003,SQL Server 2005,Visual Studio .Net 2005,SharePoint Service 2.0,ADAM……如果仔细阅读了文档以后安装应该没什么难点……比在redhat下装Oracle 9i容易多了……
开心如果有兴趣的话来给我留言……
.Net Framework 1.1 SP 1和.Net Framework 1.0 SP 3出来有一段时间了……修复了不少bug,但新带来的问题也很多,多得让人头疼……具体就不多说了……
两个关于安装的小tips:
Error message 1:
An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
Additional information: Exception has been thrown by the target of an invocation.
(发生于双击EXE之后)
Resolution:
手动把EXE解开:
c:\NDP1.1sp1-KB867460-X86.exe /xp:c:\
安装Windows Installer Patch:
msiexec /p c:\S867460.msp /l*v c:\sp1.log REINSTALL=ALL
Error message 2:
Info 9002.Microsoft .NET Framework 1.1 Service Pack 1 (KB867460) cannot be installed because you have one or more hot fixes installed. Remove them and try again.
最有可能是这个导致的:
October 2003 .NET Framework SDK Documentation Update
http://support.microsoft.com/?id=827821
Resolution:
备份并删除下面的键:
HKLM\SOFTWARE\Microsoft\Updates\.NETFramework\1.1\M827821
不排除有其他可能性,具体可以按Error 1的步骤得到installation log (“c:\sp1.log”)并进行分析……
周末花了整一天搞Web Service Interop,Apache SOAP的Web Service,要求用.Net写Web Service Client。没有WSDL……
简单的Web Method很好写,无非是RPC/Encoded style:
[System.Web.Services.WebServiceBindingAttribute(Name="urn:WareHouse",
Namespace="http://www.dummy.com.tw:8080/soap/servlet/rpcrouter")]
public class WareHouse: System.Web.Services.Protocols.SoapHttpClientProtocol
{
public WareHouse()
{
this.Url = "http://www.dummy.com.tw:8080/soap/servlet/rpcrouter";
}
[System.Web.Services.Protocols.SoapRpcMethodAttribute
(RequestNamespace="urn:WareHouse",
ResponseNamespace="urn:WareHouse"
)]
public string
setItem(
[XmlElement("itemString")]
string s)
{
object [] results = this.Invoke("setItem",new object[] {s});
return ((string)(results[0]));
}
}
这里的"setItem"方法接受一个名为"itemString"的string参数并返回一个string。
可偏偏还要求有下面这样复杂的request:
<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:setItem xmlns:ns1="urn:WareHouse" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<xml SOAP-ENV:encodingStyle="http://xml.apache.org/xml-soap/literalxml">
<RootElement>
<Version>2.0</Version>
<Date>2004-09-15T05:22:24Z</Date>
<Source />
<ROWSET>
<ROW num="1">
<A>101</A>
<B>0</B>
</ROW>
</ROWSET>
</RootElement>
</xml>
</ns1:setItem>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
这是Java client发出的request,这个setItem方法有一个名为"xml"的参数……我看下来都不知道到底是SOAP Encoding还是Literal XML…看到SOAP-ENV:encodingStyle="http://xml.apache.org/xml-soap/literalxml" 吧象是SOAP Encoding,但是setItem传递的参数却是一个名为"xml"的literal XML…又象是RPC/Literal Wrapped…两种都简单试了一下,都有问题:
1.如果是纯粹的SOAP Encoding,肯定不能靠这么的XML传复杂类型,不符合规范……
2.如果是纯粹的literal wrapped,一点没有SOAP-ENV:encodingStyle="http://xml.apache.org/xml-soap/literalxml", Server端会有一个java.lang.IllegalArgumentException: I only know how to serialize an 'org.w3c.dom.Element'. 参考了一下Apache SOAP的文档,感觉是Server想把string return value也序列化成literal XML返回,结果出了错……而如果在方法("setItem")上标有SOAP Encoding style(即上面的Attribute),那么Server就会把return value以SOAP Encoding(xsi:type="xsd:string")返回。
最后为了赶时间只好动用了SoapExtension,在SoapMessageStage.AfterSerialize阶段手动修改了request message才发送给server,实在是很丑陋……
像这样的request,不知道.Net client到底应该怎么写法……
实在没什么玄妙的……如果是.Net的ServicedComponent,那么我们可以在Setup Project的custom action中直接运行RegSvcs.exe……做的更漂亮点的就直接用System.EnterpriseServices.RegistrationHelper……
如果是VB 6或者VC写的unmanaged COM component,要装到COM+ Catalog里去,可以有两个选择:
1. 如果自认是个Windows Installer高手,那就用Orca直接修改Windows Installer提供的Complus table。
2. 如果觉得手动修改MSI太麻烦,那就在custom action里automate COM+ Administration Library,详细步骤见下:
How to install unmanaged COM+ component in VS.Net Setup project with COMAdmin Library
http://blog.joycode.com/tingwang/articles/33165.aspx
We can achieve the goal with two custom actions that automate the COMAdmin Library.
First add the unmanaged COM DLL into the Setup project and set its "Register" property to "vsdrfCOM".
Add the following two scripts to the Setup project's custom action. Set the "CustomActionData" for "Install.vbs" to "[TARGETDIR]" (without quotation marks), so that we can pass the installation target path into the custom action.
For the "Install" custom action:
=================Install.vbs=====================================
Const APPLICATION_NAME = "MyTestCOMPlusApplication"
Const COMPONENT_PROGID = "Project1.Class1"
Dim ComponentLocation
'Retrieve the custom action data. The syntax is different from managed installer class
ComponentLocation = Property("CustomActionData") & "Project1.dll"
Dim Catalog
Set Catalog = CreateObject("COMAdmin.COMAdminCatalog")
Dim ApplicationsCollection
Set ApplicationsCollection = Catalog.GetCollection("Applications")
Dim Application
Set Application = ApplicationsCollection.Add
Application.Value("Name") = APPLICATION_NAME
Application.Value("Activation") = 0 '0 - InProc, 1 - Local (OutOfProc)
'Visit the following link for more options for an Application:
'Applications
'http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cossdk/htm/comadmincollections_2ram.asp?frame=true
'Save the changes before proceeding
ApplicationsCollection.SaveChanges
'Install the Component
Catalog.InstallComponent Application.Key, ComponentLocation, "", ""
Dim ComponentsCollection
Set ComponentsCollection = ApplicationsCollection.GetCollection("Components", Application.Key)
'Populate the collection
ComponentsCollection.Populate
'Enumerate the collection and look for the Component we just installed, so that we can change the settings
Dim Component
For Each Component In ComponentsCollection
If Component.Value("ProgID") = COMPONENT_PROGID Then Exit For
Next
Component.Value("JustInTimeActivation") = False
'Visit the following link for more options for a Component:
'Components
'http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cossdk/htm/comadmincollections_4nji.asp?frame=true
'Save the changes
ComponentsCollection.SaveChanges
====================================================
For the "Uninstall" custom action:
=================Uninstall.vbs=====================================
Const APPLICATION_NAME = "MyTestCOMPlusApplication"
Dim Catalog
Set Catalog = CreateObject("COMAdmin.COMAdminCatalog")
Dim ApplicationsCollection
Set ApplicationsCollection = Catalog.GetCollection("Applications")
ApplicationsCollection.Populate
Dim Application
Dim Index, Counter
'Enumerate the collection and look for the application to delete
Index = -1
For Counter = 0 To ApplicationsCollection.Count - 1
Set Application = ApplicationsCollection.Item(Counter)
If Application.Value("Name") = APPLICATION_NAME Then
Index = Counter
Exit For
End If
Next
'Application not found. It has been deleted already?
If Index <> -1 Then
ApplicationsCollection.Remove Index
ApplicationsCollection.SaveChanges
End If
===========================================================
偶然找到一个有Windows 2.0的VPC。在大谈Longhorn和Avalon的今日,见过这个“先驱”的人恐怕不多……(我没认错吧?)
普通的ActiveX控件或COM DLL打包成Cab后能很容易通过Internet Component Download装到客户端。我们只需在网页里加入<object codebase...></object>。
今天遇到一个人想对.Net写的“COM组件”做相同的事情。可是.Net assembly是要RegAsm之后才能被COM client调用的……
一个解决办法是把.Net assembly和它的TLB(Register属性都为vsdrfCOM)打在Setup project中生成一个MSI,然后再把MSI打在CAB中。我们可以通过CAB INF中的Hook功能,在客户端下载完CAB之后自动安装CAB中的MSI。
详细步骤可见:How to deploy a .Net assmebly for COM use through CAB on Web Page
不知道有没有更好的办法……
To create a .Net assembly which will be used by COM clients:
1. Create a VB.Net Class Library named “ClassLibraryVBActiveX”. Use the following code for the project:
Imports System.Runtime.InteropServices
<GuidAttribute("BC2E2273-E3D8-4AEA-8A4F-799574803D89"), ProgIdAttribute("ClassLibraryVBActiveX.Class1")> _
Public Class Class1
Public Function Hello() As String
Return "Hello World!"
End Function
End Class
Please keep a note of the GUID. We will use it later.
2. Right click the class library project in the Solution Explorer and select “Properties”. This will open the Property Pages of the class library project. Under the category “Configuration Properties” | “Build”, check the check box “Register for COM Interop”.
3. Build the class library project.
To create a Setup project for the class library:
4. Right-click the solution in the Solution Explorer and select “Add” | “New Project”. Select “Setup Project” and name it as “ActiveXInstaller”.
5. Add the “Primary output” of the class library to the Setup project.
6. Build the Setup project. We should see that “ClassLibraryVBActiveX.dll” and “ClassLibraryVBActiveX.tlb” have been built into the Setup project.
To build the CAB file for the Setup project:
7. Extract the ZIP attached and save the files to a temporary folder.
<Attachment is not available>
We have the following 5 files:
makecab.exe – The utility for building CABs. The utility can typically be found in “System32”.
Installer.inf – The Inf file for the CAB file. It has the following contents:
[Setup Hooks]
hook1=hook1
[hook1]
run= msiexec /i %EXTRACT_DIR%\ActiveXInstaller.msi /qn
[Version]
; This section is required for compatibility on both Windows 95 and Windows NT.
Signature="$CHICAGO$"
AdvancedInf=2.0
Here “run” specifies the command we would like to run after IE extracts the CAB. The command line will install the MSI “ActiveXInstaller.msi” under silent mode. You may refer to the following links for more details about the format of INF:
About INF File Architecture
http://msdn.microsoft.com/library/default.asp?url=/workshop/delivery/download/overview/infarchitecture.asp?frame=true
Cab.DDF – a text file with the following contents:
.OPTION EXPLICIT
.Set Cabinet=on
.Set Compress=on
.Set MaxDiskSize=CDROM
.Set ReservePerCabinetSize=6144
.Set DiskDirectoryTemplate="."
.Set CompressionType=MSZIP
.Set CompressionLevel=7
.Set CompressionMemory=21
.Set CabinetNameTemplate="ActiveX.CAB"
"Installer.inf"
"ActiveXInstaller.msi"
This file is a directive file for the “MakeCAB.exe” utility. “ActiveX.CAB” is the output name of the CAB. The last two lines indicate the two files we would like to include into the CAB.
Make.bat – a batch file that will run the following command to build the CAB:
MAKECAB.EXE /f "Cab.DDF"
TestPage.htm – a template HTML that will install the CAB:
<HTML>
<BODY>
<OBJECT ID="ClassLibraryVBActiveX"
CLASSID="CLSID:BC2E2273-E3D8-4AEA-8A4F-799574803D89"
CODEBASE="ActiveX.CAB#version=1,0,0,0">
</OBJECT>
</BODY>
</HTML>
Note that the GUID should match the GUID in our VB.Net code.
8. To use these files, please copy output MSI of the Setup project (e.g. “ActiveXInstaller.msi”) to the same folder as the files above. Then double-click “Make.bat” to build the CAB.
9. Copy the CAB as well as “TestPage.htm” to a web folder and test it.
Note that this approach also provides a way for the end user to uninstall the assembly. We can remove it by “Add/Remove Programs” applet.
1. RegMon:
http://www.sysinternals.com/ntw2k/source/regmon.shtml
简介:捕捉注册表访问活动。对于以下的error message一击必杀:
Cannot create ActiveX Component
Class not registered
2. FileMon:
http://www.sysinternals.com/ntw2k/source/filemon.shtml
简介:捕捉文件访问活动。治以下问题:
Access is denied.
File Not Found.
File or assembly name , or one of its dependencies, was not found.
(比fusion log更有效,尤其是对于P/Invoke的Managed C++ assembly.)
3. ListDlls
http://www.sysinternals.com/ntw2k/freeware/listdlls.shtml
简介:列出process所加载DLL的详细信息,比windbg的lmv还少点……Typical scenario:程序在开发机上运行正常,deploy之后老crash。专治dll hell。(实现很简单,有兴趣可以自己写写看……)
4. Process Explorer
http://www.sysinternals.com/ntw2k/freeware/procexp.shtml
简介:能显示关于process的详细信息(handles, modules, threads, environment variables, security flags...),其功能涵盖了ListDlls以及一些其他工具……常用于找出文件或注册表键被哪一个process使用……
5. TCPView
http://www.sysinternals.com/ntw2k/source/tcpview.shtml
简介:察看端口使用情况。常见用途:WinSocket和IIS。找出哪个process使用了指定的端口。
(再说下去要失业了……)
托开心的福,能在这里安一个家……刚搬了些自己的老帖来……以后有时间一定多更新……
笨人对.Net和Windows Installer的兴趣比较大,但比较水,各位成名大侠多多关照……
如果要render出滚动条,我们可以把DataGrid放在<div>里。如果还要一个不随滚动条滚动的Header,我们可以在<div>外另外render一个Header。我写过一个Web Control Library详见:
Fixed Header Scrollable DataGrid control
http://blog.joycode.com/tingwang/articles/32789.aspx
(最可恨的customer居然要pager和滚动条同时用……)
许多.Net/ASP.Net的程序都用MSDE作为后台的数据库。MSDE engine的安装包里带了一套Merge Module,可以用在Setup Project中。这样我们在装自己的程序的时候附带的MSDE engine也能被装上了。
不过Setup Project build之后还是需要用Orca做一些细调,不然安装无法成功。详细步骤见link:
How to use MSDE Merge Modules in VS.Net Setup project
http://blog.joycode.com/tingwang/articles/32791.aspx
(是原创的……由于是给老外写的,所以用英语了……懒得翻译……)
除了使用MSDE Merge Module以外,我们也可以通过一个bootstrap程序把一连串的MSI连在一起安装:
MSDE 2000 Deployment Resource Kit
http://www.microsoft.com/sql/msde/techinfo/reskit/deployreskit.asp
不过现在这个Resource Kit还只是Release Candidate。
To create a simple Setup Project:
1. Open VS.Net and create a new Windows Application project.
2. Open the menu “File” | “Add Project” | “New Project”. Select “Setup Project” under “Setup and Deployment Projects”.
3. Add the “Primary Output” of the Windows Application to the Setup Project.
To add the MSDE merge modules to the Setup Project:
4. Right click the Setup Project (e.g. “Setup1”) in the Solution Explorer and select “Add” | “Merge Module”.
5. Locate to the “MSM” folder which contains all the “.MSM” files. (Typically, this path is X:\MSDE\MSM where X is the drive letter of your CD drive.)
6. Press “CTRL + A” to select all the files in that folder. Click the “Open” button to add them to your Setup Project.
7. Repeat the steps 5 and 6 with the folder “MSM\1033”. Add all the “.MSM” files in the folder “MSM\1033” to the Setup Project as well.
8. Build the Setup Project.
To use Orca to manually modify the MSI:
9. Locate the MSI output of the Setup Project and use Orca to open it.
10. Go to the InstallExecuteSequence table.
11. By default, the Sequence of the action “GetSqlStates.XXXXXX” is 103. Let’s change it to 421, so that it is sequenced after the “StreamSuportFiles.XXXXXX” action.
12. Go to the InstallUISequence table. Change the sequence of “GetSqlStates.XXXXXX” to 421 as well.
For detailed information on the order of the 2 actions, you may refer to the following article:
PRB: The MSDE Installation May Fail and Error 126 Is Logged When You Are Using Windows Installer Software
http://support.microsoft.com/?id=321283
13. Go to the Property table. Add the following 2 rows:
Property Value
SqlInstanceName InstanceName
SqlSaPwd password
Here we specify “InstanceName” as the name of the MSDE instance and “password” as the password for the SA account. For more property options, you may visit the following link:
Using SQL Server Desktop Engine Merge Modules
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/distsql/distsql_8yeq.asp?frame=true
14. Go to the InstallExecuteSequence again.
15. Change the sequence of “InstallInitialize” from 1800 to 1799.
16. Change the sequence of “RemoveExistingProducts” from 1825 to 1800.
17. Save the MSI file and close Orca.
客户要一个有滚动条的ASP.Net DataGrid控件,只好写了:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Web.UI.Design.WebControls;
using System.Text;
using System.Drawing;
[assembly:TagPrefix("Microsoft.Gtec.Dsv", "gtecdsv")]
namespace Microsoft.Gtec.Dsv
{
/// <summary>
/// Summary description for WebCustomControl1.
/// </summary>
[ToolboxData("<{0}:ScrollableFixedHeaderDataGrid runat=server></{0}:ScrollableFixedHeaderDataGrid>")]
public class ScrollableFixedHeaderDataGrid: System.Web.UI.WebControls.DataGrid
{
protected override void Render(HtmlTextWriter output)
{
//Use this flag to determine whether the component is in design-time or runtime.
//The control will be rendered differently in IDE.
//Don't bother to use DataGridDesigner.GetDesignTimeHtml
bool designMode = ((Site != null) && (Site.DesignMode));
//Backing up the properties need to change during the render process
string tempLeft = Style["LEFT"];
string tempTop = Style["TOP"];
Unit tempHeight = Height;
string tempTableStyle = Style["TABLE-LAYOUT"];
//Render a "<div>" container with scrollbars.
output.WriteBeginTag("div");
output.WriteAttribute("id",ID + "_div");
output.WriteAttribute("style",
"HEIGHT: " + Height + ";" +
//Leave 20px for the vertical scroll bar,
//assuming the end-user will not set his scroll bar to more than 20px.
"WIDTH: " + (Width.Value + 20) + "px;" +
"TOP: " + Style["TOP"] + ";" +
"LEFT: " + Style["LEFT"] + ";" +
"POSITION: " + Style["POSITION"] + ";" +
"OVERFLOW-X: auto;" +
"Z-INDEX: " + Style["Z-INDEX"] + ";" +
//Render the scrollbar differently for design-time and runtime.
"OVERFLOW-Y: " + (designMode?"scroll":"auto")
);
output.Write(HtmlTextWriter.TagRightChar);
//The DataGrid is inside the "<div>" element, so place it at (0,0).
Style["LEFT"] = "0px";
Style["TOP"] = "0px";
//Render the DataGrid.
base.Render(output);
output.WriteEndTag("div");
//Restore the values
Style["LEFT"] = tempLeft;
Style["TOP"] = tempTop;
//The following rendering is only necessary under runtime. It has negative impact during design time.
if (!designMode)
{
//Render another copy of the DataGrid with only headers.
//Render it after the DataGrid with contents,
//so that it is on the top. Z-INDEX is more complex here.
//Set Height to 0, so that it will adjust on its own.
Height = new Unit("0px");
StringWriter sw = new StringWriter();
HtmlTextWriter htw = new HtmlTextWriter(sw);
//This style is important for matching column widths later.
Style["TABLE-LAYOUT"] = "fixed";
base.Render(htw);
StringBuilder sbRenderedTable = sw.GetStringBuilder();
htw.Close();
sw.Close();
Debug.Assert((sbRenderedTable.Length > 0),
"Rendered HTML string is empty. Check viewstate usage and databinding.");
string temp = sbRenderedTable.ToString();
if (sbRenderedTable.Length > 0)
{
//AllowPaging at the top?
if ((AllowPaging) && ((PagerPosition.Top == PagerStyle.Position || (PagerPosition.TopAndBottom == PagerStyle.Position))))
{
Trace.WriteLine(temp);
sbRenderedTable.Replace(ID,ID + "_Pager", 0, (temp.IndexOf(ID) + ID.Length));
temp = sbRenderedTable.ToString();
string pager = temp.Substring(0, temp.ToLower().IndexOf(@"</tr>") + 5);
Trace.WriteLine(pager);
output.Write(pager);
output.WriteEndTag("table");
//Start of pager's <tr>
int start = temp.ToLower().IndexOf(@"<tr");
//End of pager's </tr>
int end = temp.ToLower().IndexOf(@"</tr>") + 5;
//Remove the <tr> for pager from the string. Prepare to render the headers.
sbRenderedTable.Remove(start,end-start);
Trace.WriteLine(sbRenderedTable.ToString());
sbRenderedTable.Replace(ID + "_Pager",ID + "_He