Invoking Exchange Management Shell from C# - c#

I'm currently using .NET 3.5 / C# to develop a Windows service that performs automated Exchange operations. This service basically watches a SQL database for operations to perform then spawns PowerShell and redirects the output so that results can be monitored from a UI residing elsewhere. Below is the code I'm using to invoke the process...
Action<object, DataReceivedEventArgs> DataReceived = (sender, data) =>
{
// Log data in SQL
};
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "powershell.exe"
p.StartInfo.Arguments = arguments;
// Arguments are (they're coming from SQL, didn't feel like escaping everything just for this example)
// -command ". 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'; Connect-ExchangeServer -auto; Get-Mailbox –ResultSize unlimited | Search-Mailbox -SearchQuery ... stuff ...
p.StartInfo.LoadUserProfile = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.OutputDataReceived += new DataReceivedEventHandler(DataReceived);
p.Start();
This code can do things like run ping, tracert, nslookup, echo, dir, and all of the usual command-line suspects with behavior identical to as if I typed it into a command prompt. For instance, I could copy-paste the above into the Run box and it would work flawlessly. Whenever I try to run it as above, however, I receive the following:
Get-ItemProperty : Cannot find path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup' because it does not exist.
At C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1:46 char:34
+ $global:exbin = (get-itemproperty <<<< HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup).MsiInstallPath + "bin\"
+ CategoryInfo : ObjectNotFound: (HKLM:\SOFTWARE\...erver\v14\Setup:String) [Get-ItemProperty], ItemNotFo
undException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyCommand
Get-ItemProperty : Cannot find path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup' because it does not exist.
At C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1:47 char:38
+ $global:exinstall = (get-itemproperty <<<< HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup).MsiInstallPath
+ CategoryInfo : ObjectNotFound: (HKLM:\SOFTWARE\...erver\v14\Setup:String) [Get-ItemProperty], ItemNotFo
undException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyCommand
Get-ItemProperty : Cannot find path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup' because it does not exist.
At C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1:48 char:38
+ $global:exscripts = (get-itemproperty <<<< HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup).MsiInstallPath + "scri
pts\"
+ CategoryInfo : ObjectNotFound: (HKLM:\SOFTWARE\...erver\v14\Setup:String) [Get-ItemProperty], ItemNotFo
undException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyCommand
The term 'bin\CommonConnectFunctions.ps1' is not recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1:52 char:2
+ . <<<< $global:exbin"CommonConnectFunctions.ps1"
+ CategoryInfo : ObjectNotFound: (bin\CommonConnectFunctions.ps1:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
A whole slew of other errors follows after this, but from going over the RemoteExchange PowerShell script I've determined that it all comes down to those first three errors: not being able to read from the registry. Does anyone have any idea as to why this might be happening?
Things I've tried to get this to work:
Running this code in a console app as opposed to a service context.
Every time I've run it I've done so as a domain and Exchange admin and I never got an UAC prompts, so I doubt the issue is one of credentials
Checked registry keys... The HKLM key it's looking at also has full read permissions granted to everbody
I've enabled unsigned PowerShell script execution on the server
Putting the command into a PowerShell script and invoking that programmatically
Hardcoding the registry keys' values into the PowerShell script (which just gives me another set of registry read errors further down the line)
Using ShellExecute on the process (this can't be done with output redirection, which I require)
Explicitly setting environment variables on the StartInfo to match the ones in the spawning environment
To anyone that can give me a hand... thanks a billion!
***EDIT: Perhaps I should clarify the hardcoding bit. I already cracked open RemoteExchange.ps1 and set the variables that are erroring out to their correct values (as opposed to using GetProperty or whatever) and I get marginally farther:
Exception calling "TryLoadExchangeTypes" with "2" argument(s): "Unable to determine the installed file version from the
registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ExchangeServer\v14\Setup'."
At C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1:79 char:92
+ $typeLoadResult = [Microsoft.Exchange.Configuration.Tasks.TaskHelper]::TryLoadExchangeTypes <<<< ($ManagementPath, $t
ypeListToCheck)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
From what I'm surmising from looking at like 79 this isn't something I can change. It's trying to load types from a library and to do that it needs to look at the registry still, so I can't just fix a variable.

Just try to compile your program in x64.
I know it is weird but, in x86 managed powershell, some cmdlets can't see registry keys form x64 programs.
(I got the clue from: "One of these things is not like the other | Home Of The Scary DBA" : http://www.scarydba.com/2010/06/30/one-of-these-things-is-not-like-the-other/)

You can open the Exchange.ps1 that resides in \bin, and edit the variables that appear under
## EXCHANGE VARIABLEs ########################################################
Change $global:exbin, $global:exinstall, and $global:exscripts to be hard coded paths to
"C:\Program Files\Microsoft\Exchange Server\V14\bin\"
"C:\Program Files\Microsoft\Exchange Server\V14\"
"C:\Program Files\Microsoft\Exchange Server\V14\scripts\"
This is not an ideal solution, but the workaround should not impact anything else calling these variables.

Had the same problem and actually finally figured it out. My previous answer was completely wrong.
I answered it correctly here.

Related

C# Register a dll driver programmatically while runtime

Sorry if it's a duplicate question, but I couldn't find an answer.
I'm writing a program for store scales. The driver of the scales is available in .dll format. I'm using the program by referring to the driver in this .dll. Now I register .dll via CMD. So I need to register .dll while runtime or installing application. But I couldn’t set it up either by reference or by code.
Now I'm registering it up as follows.
c: [Enter]
cd \RongtaScales [Enter]
C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe file.dll [Enter]
It works this way, but customers can’t always register it that way. I need to register it along with the program.
I tried the following:
try
{
Assembly asm = Assembly.LoadFile(#"E:\rtscaledrv.dll");
RegistrationServices regAsm = new RegistrationServices();
bool bResult = regAsm.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase);
MessageBox.Show("Installed: '"+ bResult.ToString() + "'");
}
catch (Exception exs)
{
MessageBox.Show("Error: '"+ exs.ToString() +"'");
//throw;
}
This returned an error as follows:
If I try to add via Referenceses it returns the following error:
My question is, how can I register a .dll together while the program is running or installing? I can't find the exact answer from google right now.
Thanks!
UPDATED
If I try with batch file it also doesn't work:
#echo off
setlocal
set "RegSvr32=%SystemRoot%\System32\regsvr32.exe"
set "RegAsm=%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe"
if not exist "%RegAsm%" set "RegAsm=%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe"
if not exist "%RegAsm%" set "RegAsm=%SystemRoot%\Microsoft.NET\Framework\v1.1.4322\RegAsm.exe"
rem Make note of any files which cause an exception to be thrown.
for /f "delims=" %%A in ('dir a-d /b *.dll *.ocx') do "%RegSvr32%" /s "%%~fA" && echo %%~nA SVR Registered || "%RegAsm%" /nologo /silent "%%~fA" 2>nul && echo %%~nA ASM Registered || echo %%~nA Skipped
endlocal
#pause /b 0
It returned:
rtscaledrv Skipped
Press any key to continue . . .

SSIS - Unable to load DLL 'clrcompression.dll': The specified module could not be found

I need to compress a data folder in SSIS.
For that, I use a Script task which runs this script :
public void Main()
{
// TODO: Add your code here
try
{
string zipPath = (string)Dts.Variables["User::sFolderCompressed"].Value;
string startPath = (string)Dts.Variables["User::sFolderSource"].Value;
ZipFile.CreateFromDirectory(startPath, zipPath);
}
catch (Exception objException)
{
Dts.TaskResult = (int)ScriptResults.Failure;
// Log the exception
}
Dts.TaskResult = (int)ScriptResults.Success;
}
The 2 variables I have set up:
Executing the Script Task step gives me the following error:
{System.DllNotFoundException: Unable to load DLL 'clrcompression.dll':
The specified module could not be found. (Exception from HRESULT:
0x8007007E) at Interop.inflateInit2_(Byte* stream, Int32
windowBits, Byte* version, Int32 stream_size) at
System.IO.Compression.ZipFile.Open(String archiveFileName,
ZipArchiveMode mode, Encoding entryNameEncoding) at
System.IO.Compression.ZipFile.DoCreateFromDirectory(String
sourceDirectoryName, String destinationArchiveFileName, Nullable`1
compressionLevel, Boolean includeBaseDirectory, Encoding
entryNameEncoding) at
System.IO.Compression.ZipFile.CreateFromDirectory(String
sourceDirectoryName, String destinationArchiveFileName) at
ST_19ce97c462f844559ec30884173f5a28.ScriptMain.Main() in
c:\Users\SQL\AppData\Local\Temp\3\Vsta\46892b1db29f45f2a8e1fb8c5d37a542\ScriptMain.cs:line
104}
Error message is pretty clear that I am missing a 'clrcompression.dll' file somewhere.
Can I just download this DLL? and where do I copy it to?
UPDATE
Added 'Execute Process Task' and set the following:
Executable : 'powershell.exe'
Arguments : -nologo -noprofile
-command "Compress-Archive -Path E:\Flat Files\IT\StockAge\Stock Age Difference\MAINCHECKK807Babbage\BabbageStockAgeingPartno.csv
-DestinationPath E:\Flat Files\IT\StockAge\Stock Age Difference\MAINCHECKK807Babbage\BabbageStockAgeingPartno.zip"
But getting error:
[Execute Process Task] Error: In Executing "powershell.exe" "-nologo
-noprofile -command "Compress-Archive -Path E:\Flat Files\IT\StockAge\Stock Age
Difference\MAINCHECKK807Babbage\BabbageStockAgeingPartno.csv
-DestinationPath E:\Flat Files\IT\StockAge\Stock Age Difference\MAINCHECKK807Babbage\BabbageStockAgeingPartno.zip"" at "",
The process exit code was "1" while the expected was "0".
UPDATE 2
Executing the script in PowerShell gives me the following error.
Script tasks only have access to the DLL's that have been registered in your GAC or have been manually loaded within the script. If you want to use a script task you'll need to load the DLL for the script to be able to run
Alternatively for basic zip functionality you can use a command line tools and call it from an execute process task. If you have the latest windows server running with all the .net frameworks installed you can try the PowerShell method, else use the 7zip method
PowerShell Zip Method
Set up the the execute task so that it calls PowerShell and passes your zip instruction in the arguments
Executable = 'powershell.exe'
Arguments = Powershell -nologo -noprofile -command 'Compress-Archive -Path \"C:\SO\Test Folder\Test.txt\" -DestinationPath \"C:\SO\Test Folder\Test.zip\"'
Edit 1
If your paths have spaces then you need to escape them with backslash and double quotes
Your Arguments should be
-nologo -noprofile -command 'Compress-Archive -Path \"E:\Flat Files\IT\StockAge\Stock Age Difference\MAINCHECKK807Babbage\BabbageStockAgeingPartno.csv\" -DestinationPath \"E:\Flat Files\IT\StockAge\Stock Age Difference\MAINCHECKK807Babbage\BabbageStockAgeingPartno.zip\"'
Edit 2
To debug the command try running it in PowerShell as below, see if there are any additional information
7Zip Method
Install 7zip 64bit on all the servers this package will be running on
You need to ensure that the install directory matches between servers or else ssis will not find the executable when deployed
Executable = C:\Program Files\7-Zip\7z.exe
Arguments = a -r "C:\SO\Test Folder\Test.zip" "C:\SO\Test Folder\Test.txt"
clrcompression.dll
is a part of .NET Framework.
I think you need to check .NET Framework installation on your machine.

C# Process.Start Argument with a dash fails to executr

I am having some trouble trying to work my way through some C# that I'm just not comfortable with. My issue appears to stem from a dash/hyphen in one of my parameters being passed to Process.Start(). In the below code the argument list is built from data in the database. The issue occurs when sRecipient has a dash. For example Bank-Name vs BankName. Unfortunately, I can't remove the dash because it is the name of a public key provided by the bank.
sGPGParms = " -e ";
if (sUseGPGASCIIArmor.ToUpper() == "Y")
{
sGPGParms += "-a ";
}
sGPGParms += "-r \"" + sRecipient + "\" \"" + sDestinationDir + sFileName + "\"";
The next section of code is where things seem to fail. The program is using Process.Start() to pass the path and the parameters. The program does not fall into the fail condition and says that the file created successfully but it doesn't do anything. This code worked earlier today before the bank provided a new encryption key with a dash in the name. That dash is literally the only change. This code has worked since 2008 so I have isolated it to the arguments parameter in Process.Start() and how the dash is impacting things as the culprit.
Process myCmd;
if ((myCmd = Process.Start(Path, sGPGParms)) == null)
{
LogEvent(sServerLogPath, "ERROR: GPG.exe failed to start", sUserName);
SqlContext.Pipe.Send("GPG.exe failed to start");
}
else
{
LogEvent(sServerLogPath, "Bank file created successfully", sUserName);
SqlContext.Pipe.Send("Bann file created successfully");
}
As a side note, I can run the full command copied from the logging and it works. It only fails when passed through Process.Start().
This is the command that works manually but fails when passed through Process.Start() with no public key
"C:\Program Files (x86)\GNU\GnuPG\gpg2.exe" -e -r "Bank-Name" "c:\public\BankPay.txt"
Any ideas why this dash is causing an issue?
UPDATE: Working through some of the suggestions in the comments I see that I am getting an exit code of 2 with the error public key not found. The key exists because I can run the command manually and can see it when I --list-keys

How is it possible to run this PowerShell script inside a docker container?

I'm trying to run the below script (myscript.ps1) inside a Windows Docker container without actually copying the script file to the container.
$Source = #"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
public static class Imagehlp
{
[DllImport("imagehlp.dll", CharSet = CharSet.Auto)]
public static extern int MapFileAndCheckSum(string Filename, out int HeaderSum, out int CheckSum);
}
"#
Add-Type -TypeDefinition $Source
[int] $headerSum = 0;
[int] $checkSum = 0;
$result = [Imagehlp]::MapFileAndCheckSum(
"C:\Program Files\Internet Explorer\iexplore.exe",
[ref] $headerSum,
[ref] $checkSum
)
if ($result -ne 0) {
Write-Error "Error: $result"
}
$headerSum, $checkSum
First, I have searched online for the answer and tried the solution in here. However, when I tried the given solution, I got the error below.
docker exec my-windows powershell -command "C:\Users\abc\Desktop\PowerShellScripts\myscript.ps1"
C:\Users\abc\Desktop\PowerShellScripts\myscript.ps1 : The term
'C:\Users\abc\Desktop\PowerShellScripts\myscript.ps1' is not
recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify
that the path is correct and try again.
At line:1 char:1
+ C:\Users\abc\Desktop\PowerShellScripts\myscript.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\abc...yscript.p
s1:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The reason for this error is probably because the script is in the host not in the container. Therefore, I have tried to save the script content into a variable in PowerShell and then tried to run the command with the variable.
$script1 = Get-Content ('C:\Users\abc\Desktop\PowerShellScripts\myscript.ps1')
docker exec my-windows powershell -command $script1
This time I get the following error.
At line:1 char:15
+ $Source = #" using System; using System.Diagnostics; using System.Ru ...
+ ~
No characters are allowed after a here-string header but before the end of the
line.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
ception
+ FullyQualifiedErrorId : UnexpectedCharactersAfterHereStringHeader
It says that the here-string part of my script is not properly typed and there exists another character after #" but there is no character after that. I'm guessing this is something related to newline or carriage return characters but I'm not sure. Can you please help me? Thanks a bunch!
For this, I'd suggest taking advantage of powershell.exe's -EncodedCommand switch
# Load script contents from disk
$script1 = Get-Content 'C:\Users\abc\Desktop\PowerShellScripts\myscript.ps1' -Raw
# UTF16LE-encode the script
$bytes = [System.Text.Encoding]::Unicode.GetBytes($script1)
# Convert encoded byte string to b64
$encodedCommand = [Convert]::ToBase64String($bytes)
docker exec my-windows powershell -encodedcommand $encodedCommand
Beware that Windows' process API's limit the length of command line arguments to 8191 characters, so it only works for scripts of less than ~3050 characters (including whitespace)

Jenkins build blocks because of a windows form

We have a .net application where it checks whether the build is on release mode and open up a simple windows form to input the version as a pre build event. I made this form to automatically close in 10 seconds if the user does not give an input. But unfortunately, in Jenkins, the build gets stuck on this step without going forward. So my guess was since Jenkins runs on command line it waits until the user input for continue. But even when I add automatically close the form it does not continue. Is there a way to build this job without UI blocking Jenkins?
You are not using Jenkins in the optimal way. Here are a few tips to help you out:
Get rid of your windows form to increment version
Add a CommonAssemblyInfo.cs in your visual studio solution with an initial version number
Force Jenkins to increment the version automatically [described below]
Commit the file by jenkins using git publisher or using svn.exe with commit flag
Reading Version number using powershell:
param([string]$assemblyInfoPath, [string]$workSpace)
$contents = [System.IO.File]::ReadAllText($assemblyInfoPath)
$versionString = [RegEx]::Match($contents,"(AssemblyFileVersion\("")(?:\d+\.\d+\.\d+\.\d+)(""\))")
Write-Host ("AssemblyFileVersion: " +$versionString)
$version = gc $assemblyInfoPath | select-string -pattern "AssemblyVersion"
$version -match '^\[assembly: AssemblyVersion\(\"(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<revision>[0-9]+)\.(?<build>[0-9]+)\"\)\]'
$BuildVersionNumber = $matches["major"]+"."+$matches["minor"]+"."+$matches["revision"]+"."+$matches["build"]
Write-Host ("WorkSpace: " + $env:WORKSPACE.ToString()+"\version.txt")
#[Environment]::SetEnvironmentVariable("BUILD_NUMBER", $BuildVersionNumber, "Machine")
$path = $env:WORKSPACE.ToString() + "\version.txt"
$BuildVersionNumber | out-file -encoding ASCII -filepath $path
Increment Version using powershell:
#
# This script will increment the build number in an AssemblyInfo.cs file
#
param([string]$assemblyInfoPath, [string]$workSpace)
$contents = [System.IO.File]::ReadAllText($assemblyInfoPath)
$versionString = [RegEx]::Match($contents,"(AssemblyFileVersion\("")(?:\d+\.\d+\.\d+\.\d+)(""\))")
Write-Host ("AssemblyFileVersion: " +$versionString)
#Parse out the current build number from the AssemblyFileVersion
$currentBuild = [RegEx]::Match($versionString,"(\.)(\d+)(""\))").Groups[2]
Write-Host ("Current Build: " + $currentBuild.Value)
#Increment the build number
$newBuild= [int]$currentBuild.Value + 1
Write-Host ("New Build: " + $newBuild)
#update AssemblyFileVersion and AssemblyVersion, then write to file
Write-Host ("Setting version in assembly info file ")
$contents = [RegEx]::Replace($contents, "(AssemblyVersion\(""\d+\.\d+\.\d+\.)(?:\d+)(""\))", ("`${1}" + $newBuild.ToString() + "`${2}"))
$contents = [RegEx]::Replace($contents, "(AssemblyFileVersion\(""\d+\.\d+\.\d+\.)(?:\d+)(""\))", ("`${1}" + $newBuild.ToString() + "`${2}"))
[System.IO.File]::WriteAllText($assemblyInfoPath, $contents)
$version = gc $assemblyInfoPath | select-string -pattern "AssemblyVersion"
$version -match '^\[assembly: AssemblyVersion\(\"(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<revision>[0-9]+)\.(?<build>[0-9]+)\"\)\]'
$BuildVersionNumber = $matches["major"]+"."+$matches["minor"]+"."+$matches["revision"]+"."+$matches["build"]
Write-Host ("WorkSpace: " + $env:WORKSPACE.ToString()+"\version.txt")
#[Environment]::SetEnvironmentVariable("BUILD_NUMBER", $BuildVersionNumber, "Machine")
$path = $env:WORKSPACE.ToString() + "\version.txt"
$BuildVersionNumber | out-file -encoding ASCII -filepath $path
Usage in Jenkins:
Version Format in CommonAssembly: 1.0.0.0
After incrementing: 1.0.0.1
As Tom mentioned in the comments, you should look add the option to start your forms application with a parameter that indicates that the main form should not be shown and some magic should happen. For example, check for a "/s" and let the application run silently if it is present:
MyWinformsApplication.exe /s
Also, as Tom mentioned, a console application can still open a window and this is really useful to have in Jenkins as you can then write insightful messages to the console which will be logged by Jenkins. You can always use these at a later stage to check if something went wrong.
As an additional note - if you add Console.WriteLine() to your WinForms application, Jenkins will pick the string up and add it to the console log.

Categories

Resources