regsvr32 not registering COM dispinterface - interface

My system is a Windows 10 64-bit fall creators edition with Visual Studio 2017 Professional.
I have a COM interface with the following IDL (ISomeInterface.idl):
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(61E246F6-3F22-404B-8EA8-E4D13F3206D6),
pointer_default(unique)
]
interface ISomeInterface : IUnknown
{
HRESULT DoSomething();
}
[
uuid(7EF22D33-5C29-4D32-BFBC-0B276C5F7427),
version(1.0),
helpstring("ISomeInterface 1.0 Type Library")
]
library ISomeInterfaceLib
{
importlib("stdole2.tlb");
[
uuid(E6ED57C9-002C-4C63-8780-51C7E4A436CA),
version(1.0),
helpstring("_ISomeInterfaceEvents Interface")
]
dispinterface _ISomeInterfaceEvents
{
properties:
methods:
[id(1)] HRESULT SendSomeMessage([in] BSTR sSomeMessage);
};
[
uuid(BC91D238-B0E1-4FA2-AAC8-195D761DF9DC),
version(1.0),
helpstring("ISomeInterface Class")
]
coclass ISomeInterfaceImpl
{
[default] interface ISomeInterface;
[default, source] dispinterface _ISomeInterfaceEvents;
};
};
Which I compile with this command:
midl /iid "ISomeInterface_i.c" /env win32 /h "ISomeInterface.h" /W1 /char signed /tlb "Release\ISomeInterface.tlb" /Oicf /D "NDEBUG" /robust /nologo /proxy "ISomeInterface_p.c" ISomeInterface.idl
Then I create a module definition file (ISomeInterface.def):
LIBRARY "ISomeInterface"
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
And I build my interface DLL using these commands:
cl /c /Zi /W1 /WX- /O2 /Oy- /D WIN32 /D REGISTER_PROXY_DLL /D NDEBUG /D _WINDLL /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fo"Release\\" /Fd"Release\vc100.pdb" /Gd /TC /analyze- /errorReport:prompt dlldata.c ISomeInterface_i.c ISomeInterface_p.c
link "/OUT:Release\ISomeInterface.dll" rpcns4.lib rpcrt4.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib "/DEF:ISomeInterface.def" /MANIFEST "/ManifestFile:Release\ISomeInterface.dll.intermediate.manifest" "/MANIFESTUAC:level='asInvoker' uiAccess='false'" "/PDB:Release\ISomeInterface.pdb" /OPT:REF /OPT:ICF /TLBID:1 /DYNAMICBASE /NXCOMPAT "/IMPLIB:Release\ISomeInterface.lib" /MACHINE:X86 /DLL Release\dlldata.obj Release\ISomeInterface_i.obj Release\ISomeInterface_p.obj
The interface is currently being registered using this command:
C:\Windows\SysWOW6432\regsvr32 ISomeInterface.dll
Then I generate my Interop assembly for my C# clients:
sn -k Interop.ISomeInterface.snk
tlbimp "Release\ISomeInterface.tlb" /keyfile:"Interop.ISomeInterface.snk" /out:"InteropDlls\Interop.ISomeInterface.dll" /namespace:Foo.Interop /sysarray
Which is then registered using regasm.
My issue is that the dispinterface _ISomeInterfaceEvents is not being properly registered. Everything else seems to be showing up OK in the registry. If I add these entries it all works:
[HKEY_CLASSES_ROOT\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}]
#="_ISomeInterfaceEvents"
[HKEY_CLASSES_ROOT\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\TypeLib]
#="{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}"
"Version"="1.0"
[HKEY_CLASSES_ROOT\WOW6432Node\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}]
#="_ISomeInterfaceEvents"
[HKEY_CLASSES_ROOT\WOW6432Node\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\WOW6432Node\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\TypeLib]
#="{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}"
"Version"="1.0"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}]
#="_ISomeInterfaceEvents"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{FCCDE505-1C5E-406F-BD91-068BF1E9D7AA}\TypeLib]
#="{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}"
"Version"="1.0"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}]
#="_ISomeInterfaceEvents"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\Interface\{FCCDE505-1C5E-406F-BD91-068BF1E9D7AA}\TypeLib]
#="{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}"
"Version"="1.0"
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}]
#="_ISomeInterfaceEvents"
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\Interface\{E6ED57C9-002C-4C63-8780-51C7E4A436CA}\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\Interface\{FCCDE505-1C5E-406F-BD91-068BF1E9D7AA}\TypeLib]
#="{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}"
"Version"="1.0"
What am I missing to register the dispinterface properly?
---- Edit 1
My rgs script on my ATL service looks like this:
HKCR
{
Foo.ISomeInterface.1 = s 'ISomeInterface Class'
{
CLSID = s '{BC91D238-B0E1-4FA2-AAC8-195D761DF9DC}'
}
Foo.ISomeInterface = s 'ISomeInterface Class'
{
CLSID = s '{BC91D238-B0E1-4FA2-AAC8-195D761DF9DC}'
CurVer = s 'Foo.ISomeInterface.1'
}
NoRemove CLSID
{
ForceRemove {BC91D238-B0E1-4FA2-AAC8-195D761DF9DC} = s 'ISomeInterface Class'
{
ProgID = s 'Foo.ISomeInterface.1'
VersionIndependentProgID = s 'Foo.ISomeInterface'
LocalServer32 = s '%MODULE%'
{
val ServerExecutable = s '%MODULE_RAW%'
}
TypeLib = s '{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}'
val AppID = s '%APPID%'
Version = s '1.0'
}
}
}

I added the following to the rgs script on my service application and the interface is now being registered when I run the /RegServer
ForceRemove {E6ED57C9-002C-4C63-8780-51C7E4A436CA} = s '_ISomeInterfaceEvents'
{
ProxyStubClsid32 = s '{00020420-0000-0000-C000-000000000046}'
TypeLib = s '{7EF22D33-5C29-4D32-BFBC-0B276C5F7427}'
{
val Version = s '1.0'
}
}

Related

How to download TFS code into local workspace using command line

Can anyone help me with the steps to download TFS code to local workspace using command line.
I tried below codes so far but got into multiple issues
var rv = ProcessHelper.Run(
LocalWorkspace,
#"C:\Program Files (x86)\Microsoft Visual
Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\tf.exe",
$"get \"https://xxxx/tfs/xyz/Project/_versionControl?path=%24%2FProject\" \"{LocalWorkspace}\"");
For the above code, it says Path is not supported or specified
Firstly, for that sample, you need to add necessary assembly references to your project, then use them like this:
using System.IO;
using System.Net;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
You may install this package to your project: Microsoft.TeamFoundationServer.ExtendedClient
There is a sample: DownloadDemo
Secondly, for TF command, you could check detail commands in a build log (TFVC repository as source), for example (checkout task):
##[command]tf vc workspace /new /location:local /permission:Public ws_2_4 /collection:http://xxx:8080/tfs/DefaultCollection/ /loginType:OAuth /login:.,*** /noprompt
##[command]tf vc workfold /unmap /workspace:ws_2_4 $/ /collection:http://xxx:8080/tfs/DefaultCollection/ /loginType:OAuth /login:.,*** /noprompt
##[command]tf vc workfold /map /workspace:ws_2_4 $/ScrumStarain2019TFVC D:\AgentTest\vsts-agent-win-x86-2.144.2\_work\2\s /collection:http://xxx:8080/tfs/DefaultCollection/ /loginType:OAuth /login:.,*** /noprompt
##[command]tf vc get /version:7 /recursive /overwrite D:\AgentTest\vsts-agent-win-x86-2.144.2\_work\2\s /loginType:OAuth /login:.,*** /noprompt
I came up with a solution to download the TFS directory into local workspace. Hope this code will get future generations.
create a local workspace in some temp folder
LocalWorkspace = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():n}");
if (!Directory.Exists(LocalWorkspace))
{
Directory.CreateDirectory(LocalWorkspace);
}
ProcessHelper.Run(
LocalWorkspace,
#"C:\Program Files (x86)\Microsoft Visual
Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team
Explorer\tf.exe",
$"workspace /new /noprompt /collection:<your project url> /location:local
<workspace name>")
Get the latest version of TFS into that local workspace
ProcessHelper.Run(
LocalWorkspace,
#"C:\Program Files (x86)\Microsoft Visual
Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team
Explorer\tf.exe",
$"get <TeamProject> /recursive"));
ProcessHelper.Run():
public sealed class ProcessHelper
{
private static string outputData;
public static string Run(string workingDirectory, string executable, string arguments)
{
outputData = string.Empty;
try
{
using Process proc = new Process();
proc.StartInfo.WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), workingDirectory);
proc.StartInfo.FileName = executable;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.CreateNoWindow = true;
proc.ErrorDataReceived += DataReceived;
proc.OutputDataReceived += DataReceived;
proc.EnableRaisingEvents = true;
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
}
catch (Exception ex)
{
return $"{executable} | {workingDirectory} | {arguments}{Environment.NewLine}{Environment.NewLine}{ex}";
}
return $"{executable} | {workingDirectory} | {arguments}{Environment.NewLine}{Environment.NewLine}{outputData}";
}
static void DataReceived(object sender, DataReceivedEventArgs e)
{
string data = e.Data;
if (!String.IsNullOrEmpty(data))
{
Console.WriteLine(data);
outputData += data;
}
}
}

Pass multiple parameters/arguments to target

I've got a target Release-Prepare taking the version as an argument:
var version = Argument("version", "1.0.0.0");
Task ("Release-Prepare")
.Does (() => {
// Compute the package version
var gitCommitHash = GitLogTip(projectDir).Sha;
var gitVersion = gitCommitHash?.Substring(0, 8) ?? "Unknown";
var currentDateTime = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
var packageVersion = $"{version}-{gitVersion}-{currentDateTime}";
Information(packageVersion);
// Versions having to be modified in *.csproj
var versionXPath = "/Project/PropertyGroup/Version";
var assemblyVersionXPath = "/Project/PropertyGroup/AssemblyVersion";
var fileVersionXPath = "/Project/PropertyGroup/FileVersion";
// Set versions for all projects (excpet testing)
var projectFilePaths = GetFiles("./**/*.csproj");
foreach(var projectFilePath in projectFilePaths) {
if(projectFilePath.FullPath.Contains("Tests")){
// Do not version test projects
continue;
}
XmlPoke(projectFilePath, versionXPath, packageVersion);
XmlPoke(projectFilePath, assemblyVersionXPath, version);
XmlPoke(projectFilePath, fileVersionXPath, version);
}
});
Executing the target without parameters works fine:
PS E:\dev\Sppd.TeamTuner> powershell -ExecutionPolicy ByPass -File build.ps1 -script "e:\dev\Sppd.TeamTuner\build.cake" -target "Release-Prepare" -verbosity normal
Preparing to run build script...
Running build script...
========================================
Release-Prepare
========================================
1.0.0.0-af9d218d-20191125093743
Task Duration
--------------------------------------------------
Release-Prepare 00:00:02.3258569
--------------------------------------------------
Total: 00:00:02.3258569
But I can't figure out how to pass the target AND the version using a PowerShell command. I've tried:
powershell -ExecutionPolicy ByPass -File build.ps1 -script "e:\dev\Sppd.TeamTuner\build.cake" -target "Release-Prepare" -version="1.2.3.4" -verbosity normal -> Error: Argument value is not a valid boolean value.
powershell -ExecutionPolicy ByPass -File build.ps1 -script "e:\dev\Sppd.TeamTuner\build.cake" -ScriptArgs '--target=Release-Prepare','--version=1.3.1.2' -verbosity normal -> Error: The target 'Release-Prepare,--version=1.3.1.2' was not found.
I've tried all permutations of -ScriptArgs '--target=Release-Prepare','--version=1.3.1.2' I could think of (single dash, space/comma, single/double quotes. But everything I've tried resulted in cake intzerpreting as a single command.
How do ScriptArgs have to be specified to work for multiple parameters?
I am going to assume that you are using the latest bootstrapper file, which is available from here:
https://cakebuild.net/download/bootstrapper/windows
NOTE: If this is not the case, then the way that the arguments are parsed and sent to Cake might be different than what I am showing here.
You can download this using:
Invoke-WebRequest https://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1
As mentioned here.
With that in place, let's use the following build.cake file:
///////////////////////////////////////////////////////////////////////////////
// ARGUMENTS
///////////////////////////////////////////////////////////////////////////////
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var version = Argument("applicationVersion", "1.0.0.0");
///////////////////////////////////////////////////////////////////////////////
// SETUP / TEARDOWN
///////////////////////////////////////////////////////////////////////////////
Setup(ctx =>
{
// Executed BEFORE the first task.
Information("Running tasks...");
});
Teardown(ctx =>
{
// Executed AFTER the last task.
Information("Finished running tasks.");
});
///////////////////////////////////////////////////////////////////////////////
// TASKS
///////////////////////////////////////////////////////////////////////////////
Task("Default")
.Does(() => {
Information("Configuration: {0}", configuration);
Information("Target: {0}", target);
Information("Version: {0}", version);
});
Task("Release-Prepare")
.Does(() =>
{
Information("Configuration: {0}", configuration);
Information("Target: {0}", target);
Information("Version: {0}", version);
});
RunTarget(target);
If we run this with no arguments, we will get this as the output:
========================================
Default
========================================
Configuration: Release
Target: Default
Version: 1.0.0.0
However, if we run the following:
.\build.ps1 --target=Release-Prepare --configuration=Debug --applicationVersion=2.2.2.2
We will get:
========================================
Release-Prepare
========================================
Configuration: Debug
Target: Release-Prepare
Version: 2.2.2.2
One thing to mention compared to what you have...
The version argument is already one that is reserved by the Cake.exe directly, i.e. running:
cake.exe --version
Will output the version number of Cake itself. That is why I switched to using applicationVersion as the argument name, rather than version.

How to change the Console Ouptut Mode using SetConsoleMode in powershell?

I am trying to change the Windows Console Mode for output (CONOUT$) using the Windows API and SetConsoleMode calls. So I modified a PowerShell script, based on ConinMode.ps1 (and which works for input), to do this. Reading works fine with both the ConoutMode.exe and my script, and returns:
# .\ConoutMode.exe
mode: 0x3
ENABLE_PROCESSED_OUTPUT 0x0001 ON
ENABLE_WRAP_AT_EOL_OUTPUT 0x0002 ON
ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 off <====
DISABLE_NEWLINE_AUTO_RETURN 0x0008 off
ENABLE_LVB_GRID_WORLDWIDE 0x0010 off
# .\ConoutMode.ps1
OK! GetConsoleWindow handle : 0x1F06D6
Console Input Mode (CONIN) : 0x21f
Console Output Mode (CONOUT) : 0x3
However, both my script and the exe fails in writing the mode. (Possibly because it thinks that my output handle is not pointing to a console?)
In PowerShell:
# .\ConoutMode.exe set 0x000f
error: SetConsoleMode failed (is stdout a console?)
# .\ConoutMode.ps1 -Mode 7
OK! GetConsoleWindow handle : 0x1F06D6
old (out) mode: 0x3
SetConsoleMode (out) failed (is stdout a console?)
At C:\cygwin64\home\emix\win_esc_test\ConoutMode.ps1:112 char:9
+ throw "SetConsoleMode (out) failed (is stdout a console?)"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (SetConsoleMode ...out a console?):String) [], RuntimeException
+ FullyQualifiedErrorId : SetConsoleMode (out) failed (is stdout a console?)
Here is my ConoutMode.ps1 script in it's entirety:
param (
[int] $Mode
)
$signature = #'
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr GetConsoleWindow();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
public const int STD_INPUT_HANDLE = -10;
public const int STD_OUTPUT_HANDLE = -11;
public const int ENABLE_PROCESSED_OUTPUT = 0x0001;
public const int ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002;
public const int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
public const int DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
public const int ENABLE_LVB_GRID_WORLDWIDE = 0x0010;
'#
#----------------------------------------------------------
$WinAPI = Add-Type -MemberDefinition $signature -Name WinAPI -Namespace ConoutMode -PassThru
#$WinAPI = Add-Type -MemberDefinition $signature -Name WinAPI -Namespace IdentifyConsoleWindow -PassThru
$hwnd = $WinAPI::GetConsoleWindow()
if ($hwnd -eq 0) {
throw "Error: GetConsoleWindow returned NULL."
}
echo "OK! GetConsoleWindow handle : 0x$($hwnd.ToString("X"))"
function GetConIn {
$ret = $WinAPI::GetStdHandle($WinAPI::STD_INPUT_HANDLE)
if ($ret -eq -1) {
throw "Error: cannot get stdin handle"
}
return $ret
}
function GetConOut {
$ret = $WinAPI::GetStdHandle($WinAPI::STD_OUTPUT_HANDLE)
if ($ret -eq -1) {
throw "Error: cannot get stdout handle"
}
return $ret
}
function GetConInMode { #GetCOnsoleMode
$conin = GetConIn
$mode = 0
$ret = $WinAPI::GetConsoleMode($conin, [ref]$mode)
if ($ret -eq 0) {
throw "GetConsoleMode (in) failed (is stdin a console?)"
}
return $mode
}
function GetConOutMode {
$conout = GetConOut
$mode = 0
$ret = $WinAPI::GetConsoleMode($conout, [ref]$mode)
if ($ret -eq 0) {
throw "GetConsoleMode (out) failed (is stdout a console?)"
}
return $mode
}
function SetConInMode($mode) {
$conin = GetConIn
$ret = $WinAPI::SetConsoleMode($conin, $mode)
if ($ret -eq 0) {
throw "SetConsoleMode (in) failed (is stdin a console?)"
}
}
function SetConOutMode($mode) {
#$conout = GetConOut
# Different tack:
$conout = $hwnd
$ret = $WinAPI::SetConsoleMode($conout, $mode)
if ($ret -eq 0) {
throw "SetConsoleMode (out) failed (is stdout a console?)"
}
}
#----------------------------------------------------------
# MAIN
#----------------------------------------------------------
$oldInMode = GetConInMode
$oldOutMode = GetConOutMode
$newMode = $oldOutMode
$doit = $false
if ($PSBoundParameters.ContainsKey("Mode")) {
$newMode = $Mode
$doit = $true
}
if ($doit) {
echo "old (out) mode: 0x$($oldOutMode.ToString("x"))"
SetConOutMode $newMode
$newMode = GetConOutMode
echo "new (out) mode: 0x$($newMode.ToString("x"))"
} else {
echo "Console Input Mode (CONIN) : 0x$($oldInMode.ToString("x"))"
echo "Console Output Mode (CONOUT) : 0x$($oldOutMode.ToString("x"))"
}
So at the end of the day, I want to enable ENABLE_VIRTUAL_TERMINAL_PROCESSING for the Console.
The system I am using for this is:
---------------------------------------------------------
PowerShell Version : 6.1.1
OS Name : Microsoft Windows 8.1 (64-bit)
OS Version : 6.3.9600 [2018-11-16 00:50:01]
OS BuildLabEx : 9600.19179
OS HAL : 6.3.9600.18969
OS Kernel : 6.3.9600.18217
OS UBR : 19182
-------------------------------------------------------
with Privilege : Administrator
-------------------------------------------------------
ExecutionPolicy :
MachinePolicy : Undefined
UserPolicy : Undefined
Process : Undefined
CurrentUser : Undefined
LocalMachine : Bypass
Console Settings:
Type : ConsoleHost
OutputEncoding : Unicode (UTF-8)
Color Capability : 23
Registry VT Level : 1
CodePage (input) : 437
CodePage (output) : 437
I have also enabled the registry item: HKCU:\Console\VirtualTerminalLevel, as instructed elsewhere.
Q: How can I enable this bit/flag using PowerShell?
Some things I can think of that may be wrong:
I surely have the wrong understanding of how this works...
I might have the wrong permissions to write to console? (How to set?)
I might not write to the correct output buffer? (How to find?)
Note that setting the output mode of one screen buffer does not affect the output mode of other screen buffers.
When executing a script, perhaps a new output buffer is created? (What to do?)
I might have to create a new buffer?
I might have to create a new Console?
Related Questions and Links:
ENABLE_VIRTUAL_TERMINAL_PROCESSING and DISABLE_NEWLINE_AUTO_RETURN failing
MSDN: Inside the Windows Console
MSDN: Introducing the Windows Pseudo Console - ConPty
The code is using "GetStdHandle" to get stdin or stdout. And those might be console handles - but it's dependent on how the process is created. If you're launched from a console, and the stdin/stdout aren't redirected, then you'd probably get a console handle. But it's not guaranteed.
Rather than asking for stdin/stdout, just open the console handles directly - you can "CreateFile" for CONIN$ and CONOUT$. The documentation for CreateFile has a whole section on Consoles.
Update: I made a Gist to demonstrate how to use CreateFile to open CONIN$ and CONOUT$ from PowerShell.
I have been experimenting with this myself for a few days and have come to realize a few things. Apparently PowerShell enables Virtual Terminal Processing when it starts up. It disables it again, however, when it launches an executable and then reenables it when the executable exits. This means that whatever executable needs Virtual Terminal Processing must enable it itself or be called through a wrapper program that enables it and then pipes stdin/stdout/stderr through to the terminal. As for how would even go about writing such a program, I don't know.

Calling AppDomain.DoCallback from Powershell

This is based on the Stack Overflow question: How to load an assembly as reflection-only in a new AppDomain?
I am attempting to determine the runtime version of an assembly, but that assembly could be loaded multiple times as I traverse through nested folders. Loading the assembly directly using
[Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly)
will therefore not work, as the assembly can only be loaded once in the app-domain.
Given the following function to load an assembly in a separate AppDomain:
function Load-AssemblyInNewAppDomain($assembly)
{
Write-Host $assembly.FullName
$domain = [AppDomain]::CreateDomain([Guid]::NewGuid())
$domain.DoCallback
({
$loaded = [Reflection.Assembly]::Load($assembly)
$runtime = $loaded.ImageRuntimeVersion
Write-Host $runtime
})
}
This outputs the contents of the delegate to the console, rather than executing it:
OverloadDefinitions
-------------------
void DoCallBack(System.CrossAppDomainDelegate callBackDelegate)
void _AppDomain.DoCallBack(System.CrossAppDomainDelegate theDelegate)
$loaded = [Reflection.Assembly]::Load($assembly)
$runtime = $loaded.ImageRuntimeVersion
Write-Host $runtime
Note that the results are the same, whether I use PowerShell 4 or 5
Any help/guidance appreciated
First thought: don't muck around with AppDomains at all and use a completely separate process. Those are (relatively) easily launched from PowerShell, at least. The drawback is that it's potentially much slower if you're doing this for lots of files.
$myAssemblyPath = "C:\..."
$getImageRuntimeVersion = {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($input).ImageRuntimeVersion
}
$encodedCommand = [Convert]::ToBase64String(
[Text.Encoding]::Unicode.GetBytes($getImageRuntimeVersion)
)
$imageRuntimeVersion = $myAssemblyPath | powershell -EncodedCommand $encodedCommand
So, is there no way at all to do this with AppDomains in PowerShell? Well, there is, but it's not pretty. You can't use AppDomain.DoCallBack because, as you've discovered, PowerShell can't remote delegates that way (because, under the covers, it produces dynamic methods).
However, it's easy to host the PowerShell runtime, and all PowerShell objects know how to serialize (a requirement for cross-domain remoting), so invoking a PowerShell script in another AppDomain is fairly simple (but still ugly):
$scriptInvokerAssembly = [System.IO.Path]::GetTempFileName() + ".dll"
Add-Type -OutputAssembly $tempAssembly -TypeDefinition #"
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Management.Automation;
public class ScriptInvoker : MarshalByRefObject {
public IEnumerable<PSObject> Invoke(ScriptBlock scriptBlock, PSObject[] parameters) {
using (var powerShell = PowerShell.Create()) {
powerShell.Commands.AddScript(scriptBlock.ToString());
if (parameters != null) {
powerShell.AddParameters(parameters);
}
return powerShell.Invoke();
}
}
}
"#
[Reflection.Assembly]::LoadFile($scriptInvokerAssembly) | Out-Null
Function Invoke-CommandInTemporaryAppDomain([ScriptBlock] $s, [object[]] $arguments) {
$setup = New-Object System.AppDomainSetup
$setup.ApplicationBase = Split-Path ([ScriptInvoker].Assembly.Location) -Parent
$domain = [AppDomain]::CreateDomain([Guid]::NewGuid(), $null, $setup)
$scriptInvoker = $domain.CreateInstanceAndUnwrap(
[ScriptInvoker].Assembly.FullName, [ScriptInvoker]
);
$scriptInvoker.Invoke($s, $arguments)
[AppDomain]::Unload($domain)
}
And now you can do
Invoke-CommandInTemporaryAppDomain {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($args[0]).ImageRuntimeVersion
} $myAssemblyPath
Note that we have to generate a temporary assembly on disk and have AppDomain load it from there. This is ugly, but you can't have Add-Type produce an in-memory assembly, and even if you do end up with a byte[] getting that to load in another AppDomain is anything but trivial because you can't hook AppDomain.AssemblyResolve in PowerShell. If this command was packaged in a module, you'd compile the assembly containing the ScriptInvoker ahead of time, so I don't see working around this as a priority.
You can't run DoCallback via powershell alone. But DoCallBack does work with some inline C#. As Jeroen says it's ugly, but this works:
$assm = "C:\temp\so\bin\dynamic-assembly.dll"
Add-Type -TypeDefinition #"
using System.Reflection;
using System;
namespace Example
{
public class AppDomainUtil
{
public void LoadInAppDomain(AppDomain childDomain, string assemblyName)
{
childDomain.SetData("assemblyName", assemblyName);
childDomain.DoCallBack( new CrossAppDomainDelegate(LoadAssembly)) ;
}
public static void LoadAssembly()
{
string assemblyName = (string)AppDomain.CurrentDomain.GetData("assemblyName");
// console not available from another domain
string log = "c:\\temp\\hello.txt";
System.IO.File.WriteAllText(log, string.Format("Hello from {0}\r\n",AppDomain.CurrentDomain.FriendlyName));
System.IO.File.AppendAllText(log, string.Format("Assembly to load is {0}\r\n",assemblyName));
Assembly loaded = Assembly.Load(assemblyName);
System.IO.File.AppendAllText(log, string.Format("Assemblyloaded: {0}\r\n",loaded.FullName));
}
}
}
"# -OutputAssembly $assm -OutputType Library # must set output assembly otherwise assembly generated in-memory and it will break with Type errors.
Add-Type -Path $assm
function Load-AssemblyInNewAppDomain([string]$assembly) {
Write-Host "Parent domain: $([AppDomain]::CurrentDomain.FriendlyName)"
$util = New-Object Example.AppDomainUtil
$ads = New-Object System.AppDomainSetup
$cd = [AppDomain]::CurrentDomain
# set application base
$ads.ApplicationBase = [IO.path]::GetDirectoryName( $assm )
[System.AppDomain]$newDomain = [System.AppDomain]::CreateDomain([System.Guid]::NewGuid().ToString(), $null, $ads);
Write-Host "Created child domain: $($newDomain.FriendlyName)"
$util.LoadInAppDomain($newDomain, $assembly)
}
Testing it out:
PS C:\WINDOWS\system32> Load-AssemblyInNewAppDomain "".GetType().Assembly.FullName
Parent domain: PowerShell_ISE.exe
Created child domain: 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
PS C:\WINDOWS\system32> cat C:\temp\hello.txt
Hello from 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
Assembly to load is mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Assemblyloaded: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

How can I get PowerShell Added-Types to use Added Types

I'm working on a PoSh project that generates CSharp code, and then Add-Types it into memory.
The new types use existing types in an on disk DLL, which is loaded via Add-Type.
All is well and good untill I actualy try to invoke methods on the new types. Here's an example of what I'm doing:
$PWD = "."
rm -Force $PWD\TestClassOne*
$code = "
namespace TEST{
public class TestClassOne
{
public int DoNothing()
{
return 1;
}
}
}"
$code | Out-File tcone.cs
Add-Type -OutputAssembly $PWD\TestClassOne.dll -OutputType Library -Path $PWD\tcone.cs
Add-Type -Path $PWD\TestClassOne.dll
$a = New-Object TEST.TestClassOne
"Using TestClassOne"
$a.DoNothing()
"Compiling TestClassTwo"
Add-Type -Language CSharpVersion3 -TypeDefinition "
namespace TEST{
public class TestClassTwo
{
public int CallTestClassOne()
{
var a = new TEST.TestClassOne();
return a.DoNothing();
}
}
}" -ReferencedAssemblies $PWD\TestClassOne.dll
"OK"
$b = New-Object TEST.TestClassTwo
"Using TestClassTwo"
$b.CallTestClassOne()
Running the above script gives the following error on the last line:
Exception calling "CallTestClassOne" with "0" argument(s):
"Could not load file or assembly 'TestClassOne,...'
or one of its dependencies. The system cannot find the file specified."
At AddTypeTest.ps1:39 char:20
+ $b.CallTestClassOne <<<< ()
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
What am I doing wrong?
This happens because any assemblies are looked for by the CLR loader in the application's (PowerShell's) base directory. Of course, it doesn't find your assembly there. The best way to solve this is to hook the AssemblyResolve event as stej mentions but use it to tell the CLR where the assembly is. You can't do this with PowerShell 2.0's Register-ObjectEvent because it doesn't work with events that require a return value (ie the assembly). In this case, let's use more C# via Add-Type to do this work for us. This snippet of code works:
ri .\TestClassOne.dll -for -ea 0
$resolver = #'
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Utils
{
public static class AssemblyResolver
{
private static Dictionary<string, string> _assemblies;
static AssemblyResolver()
{
var comparer = StringComparer.CurrentCultureIgnoreCase;
_assemblies = new Dictionary<string,string>(comparer);
AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler;
}
public static void AddAssemblyLocation(string path)
{
// This should be made threadsafe for production use
string name = Path.GetFileNameWithoutExtension(path);
_assemblies.Add(name, path);
}
private static Assembly ResolveHandler(object sender,
ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
if (_assemblies.ContainsKey(assemblyName.Name))
{
return Assembly.LoadFrom(_assemblies[assemblyName.Name]);
}
return null;
}
}
}
'#
Add-Type -TypeDefinition $resolver -Language CSharpVersion3
$code = #'
namespace TEST {
public class TestClassOne {
public int DoNothing() {
return 1;
}
}
}
'#
$code | Out-File tcone.cs
Add-Type -OutputAssembly TestClassOne.dll -OutputType Library -Path tcone.cs
# This is the key, register this assembly's location with our resolver utility
[Utils.AssemblyResolver]::AddAssemblyLocation("$pwd\TestClassOne.dll")
Add-Type -Language CSharpVersion3 `
-ReferencedAssemblies "$pwd\TestClassOne.dll" `
-TypeDefinition #'
namespace TEST {
public class TestClassTwo {
public int CallTestClassOne() {
var a = new TEST.TestClassOne();
return a.DoNothing();
}
}
}
'#
$b = new-object Test.TestClassTwo
$b.CallTestClassOne()
When you output the TestClassTwo to a dll (in the same directory as TestClassOne) and Add-Type it, it works. Or at least at my machine ;) So that's the ugly workaround.
When calling $b.CallTestClassOne() PowerShell tries (from some reason I don't know) to find assembly TestClassOne.dll at these locations:
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.EXE
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.EXE
This is output from fuslogvw tool. It might be useful for you. The same list of paths can bee seen live using ProcessMonitor.
You might also try this (before calling CallTestClassOne()
[appdomain]::CurrentDomain.add_assemblyResolve({
$global:x = $args
})
$b.CallTestClassOne()
$x | fl
This will show you what assembly failed and some more info.
I agree that it should work as you expect. So that's why this looks somewhat buggy.