Link to home
Start Free TrialLog in
Avatar of srexp
srexpFlag for Afghanistan

asked on

Problem creating distribution lists on exchange server 2010 via power shell

Hi there,

we have an it management system running where we can put users into special groups. Eg. every user who uses Microsoft Word is in that group. Furthermore this tool has a kind of task server that can run tasks periodically.

The idea now is, that we would like to create distribution lists on the exchange server with the mentioned groups, like "All Word Users".

The task servers has some requirements that cannot easily be changed:

* The TaskServer can run multiple tasks threaded simultaneously
* The TaskServer is a windows service that runs under a domain account with local admin rights.
* The TaskServer-User has no authorization to do anything on the exchange server
* It is possible to impersonate a task to run with appropriate rights.

That's the prehistory so far. The solution looks as follows:

* I have created this task.
* I have installed the exchange power shell commandlets
  ("Microsoft.Exchange.Management.PowerShell.E2010")
* I have created a power shell command with runspace and pipeline
* I have impersonated the command with appropriate rights

The mean thing is now, that my code functions properly when

* I run the code without any service involved in a console application.
* It also runs as a windows service hosted in a console application.
* It does not run, when the service is hosted in a windows service (installutil etc.). I get the following error message:

Das Windows PowerShell-Snap-In Microsoft.Exchange.Management.PowerShell.E2010 kann aufgrund des folgenden Fehlers nicht geladen werden: Der Typeninitialisierer für "Microsoft.Exchange.Data.Directory.Globals" hat eine Ausnahme verursacht.
   bei System.Management.Automation.Runspaces.RunspaceConfigForSingleShell.LoadCustomPSSnapIn(PSSnapInInfo mshsnapinInfo)
   bei System.Management.Automation.Runspaces.RunspaceConfigForSingleShell.LoadPSSnapIn(PSSnapInInfo mshsnapinInfo)
   bei System.Management.Automation.Runspaces.RunspaceConfigForSingleShell.LoadPSSnapIn(PSSnapInInfo mshsnapinInfo, PSSnapInException& warning)
   bei System.Management.Automation.Runspaces.RunspaceConfigForSingleShell.DoAddPSSnapIn(String name, PSSnapInException& warning)
   bei System.Management.Automation.Runspaces.RunspaceConfiguration.AddPSSnapIn(String name, PSSnapInException& warning)
   bei mpc.Utilities.PowerShellCommand.AddSnapIn(String psSnapInName) in E:\Visual Studio


--> Short Translation: "The Windows PowerShell-SnapIn Microsoft.Exchange.... could not be loaded due to an error. The Type Initialization "Microsoft.Exchange.Data.Directory.Globals" has thrown an excetion at ...."

I have already checked a possible x86/x64 issue. Both the service as well as the task are compiled with "Any CPU".

Any idea would be more then welcomed.

Thanks.


Here is my sourcecode:

 Private Sub CreateExchangeDistributionList(ByVal name As String, ByVal organizationalUnit As String, ByVal samAccountName As String)

            'StartImpersonation(ModuleLibrarySharedConfig.GetConfigValue(BindableConfiguration.StandardParameterNamesEnum.ImpersonationUserName),
            '            ModuleLibrarySharedConfig.GetConfigValue(BindableConfiguration.StandardParameterNamesEnum.ImpersonationUserDomain),
            '            ModuleLibrarySharedConfig.GetConfigValue(BindableConfiguration.StandardParameterNamesEnum.ImpersonationUserPassword))

            Dim cmd As String = String.Format("New-DistributionGroup -Name ""{0}"" -OrganizationalUnit ""{1}"" -SamAccountName ""{2}"" -Type ""Distribution""",
                                                name,
                                                organizationalUnit,
                                                samAccountName)
         
            Dim ps As New PowerShellCommand
            ps.AddSnapIn("Microsoft.Exchange.Management.PowerShell.E2010")
            ps.AddCommand(cmd)
            ps.Run()

            EndImpersonation()

            If ps.HasError Then
                Throw New Exception(ps.GetLog)
            End If

        End Sub

Public Class PowerShellCommand

        ' //// EVENTS

        ' //// DEKLARATIONEN

        Private _log As String
        Private _hasErrors As Boolean

        Private ReadOnly _runspace As Runspace
        Private ReadOnly _pipeline As Pipeline

        ' //// PROPERTIES
        
        Public ReadOnly Property HasError As Boolean
            Get
                Return _hasErrors
            End Get
        End Property
        Public ReadOnly Property GetLog As String
            Get
                Return _log
            End Get
        End Property

        ' //// EVENTHANDLER

        ' //// METHODEN

        Public Sub AddSnapIn(ByVal psSnapInName As String)

            ' Exchange Snapin
            ' "Microsoft.Exchange.Management.PowerShell.E2010"

            Dim snapInWarning As New PSSnapInException
            Dim snapInInfo = _runspace.RunspaceConfiguration.AddPSSnapIn(psSnapInName, snapInWarning)
            snapInInfo.LogPipelineExecutionDetails = True

            If snapInWarning IsNot Nothing Then
                Throw New Exception("Power Shell SnapIn Warning", snapInWarning)
            End If

        End Sub
        Public Sub AddCommand(ByVal commandText As String)

            ' "New-DistributionGroup -Name ""Mail_Abwesenheitsplaner_U"" -OrganizationalUnit ""mpc-group.com/Firmen/Groups/Mail"" -SamAccountName ""Mail_Abwesenheitsplaner_U"" -Type ""Distribution"""
            _pipeline.Commands.Add(New Command(commandText, True))

        End Sub

        ' //// INIT

        Public Sub New()
            
            Dim runspaceConfig = RunspaceConfiguration.Create()
            _runspace = RunspaceFactory.CreateRunspace(runspaceConfig)
            _pipeline = _runspace.CreatePipeline

        End Sub

        ' //// ENUMS

        ' //// INLINE KLASSEN

        Public Sub Run()

            Try

                ClearLog()

                AddLogEntry("PowerShell - Execution started at " & Now.ToString)
                AddLogEntry("Current User: " & System.Environment.UserName)

                _runspace.Open()

                Dim out = _pipeline.Invoke()
                Dim builder As New System.Text.StringBuilder

                Dim info = _pipeline.PipelineStateInfo
                If (info.State <> PipelineState.Completed) Then
                    AddLogEntry("---PipelineState: " & info.State.ToString)
                    AddLogEntry(info.Reason.ToString)
                End If

                If _pipeline.Error.Count > 0 Then
                    LogError(_pipeline.Error)
                End If

            Catch ex As Exception

                LogError(ex)

            End Try

            If _hasErrors Then
                FlushLogToEventLog(_log)
            End If

        End Sub

        Private Sub ClearLog()
            _log = ""
            _hasErrors = False
        End Sub

        Private Sub AddLogEntry(ByVal entry As String)
            _log &= entry & Environment.NewLine
        End Sub

        Private Sub LogError(ByVal pipelineError As PipelineReader(Of Object))

            Dim x As Integer
            Dim log As New System.Text.StringBuilder

            If pipelineError.Count > 0 Then

                While Not pipelineError.EndOfPipeline
                    log.AppendFormat("---- Error from pipeline [{0}]", x)
                    Dim value = TryCast(pipelineError.Read(), PSObject)
                    If value IsNot Nothing Then
                        'get the ErrorRecord
                        Dim r = TryCast(value.BaseObject, ErrorRecord)
                        If r IsNot Nothing Then
                            'build whatever kind of message your want
                            log.AppendLine(Convert.ToString(r.InvocationInfo.MyCommand.Name) & " : " & Convert.ToString(r.Exception.Message))
                            log.AppendLine(r.InvocationInfo.PositionMessage)
                            log.AppendLine(String.Format("+ CategoryInfo: {0}", r.CategoryInfo))
                            log.AppendLine(String.Format("+ FullyQualifiedErrorId: {0}", r.FullyQualifiedErrorId))
                        End If
                    End If
                End While
            End If

            LogError(log.ToString)

        End Sub
        Private Sub LogError(ByVal ex As Exception)

            LogError(ex.Message)

            If ex.InnerException IsNot Nothing Then
                LogError(ex.InnerException)
            End If

        End Sub
        Private Sub LogError(ByVal err As String)

            AddLogEntry(err)
            _hasErrors = True

        End Sub

        Public Shared Sub FlushLogToEventLog(ByVal log As String)

            Dim sSource As String
            Dim sLog As String
            Dim sEvent As String
            Dim sMachine As String

            sSource = "PowerShell Log"
            sLog = "Application"
            sEvent = log
            sMachine = "."

            If Not EventLog.SourceExists(sSource, sMachine) Then
                EventLog.CreateEventSource(sSource, sLog, sMachine)
            End If

            Dim eLog As New EventLog(sLog, sMachine, sSource)
            eLog.WriteEntry(sEvent, EventLogEntryType.Error, 234, CType(3, Short))

        End Sub

    End Class

Open in new window

Avatar of Neil Russell
Neil Russell
Flag of United Kingdom of Great Britain and Northern Ireland image

Your code for StartImpersonation(...) is all commented out. Is that just a typo in your posting??
Avatar of srexp

ASKER

That's a typo in my posting. This should not be commented out. The error occurs with impersonation to.

And as I said. The TaskServer User does not have rights to create lists on the exchange server. The code that gets executed in the three metioned scenarios is the same. Tow are working one not.

Here is the code I'm using for the impersonation:

Private Sub ImpersonateValidUser(ByVal userName As String, ByVal domain As String, ByVal password As String)
            ImpersonateValidUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE)
        End Sub
        Private Sub ImpersonateValidUser(ByVal userName As String, ByVal domain As String, ByVal password As String, ByVal provider As Integer)
            Dim tempWindowsIdentity As WindowsIdentity = Nothing
            Dim token As IntPtr = IntPtr.Zero
            Dim tokenDuplicate As IntPtr = IntPtr.Zero

            Try
                If RevertToSelf() Then
                    If LogonUser(userName, domain, password, provider, LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
                        If DuplicateToken(token, 2, tokenDuplicate) <> 0 Then
                            tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
                            impersonationContext = tempWindowsIdentity.Impersonate()
                        Else
                            Throw New Win32Exception(Marshal.GetLastWin32Error())
                        End If
                    Else
                        Throw New Win32Exception(Marshal.GetLastWin32Error())
                    End If
                Else
                    Throw New Win32Exception(Marshal.GetLastWin32Error())
                End If
            Finally
                If token <> IntPtr.Zero Then
                    CloseHandle(token)
                End If
                If tokenDuplicate <> IntPtr.Zero Then
                    CloseHandle(tokenDuplicate)
                End If
            End Try
        End Sub

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of srexp
srexp
Flag of Afghanistan image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of srexp

ASKER

Not solved. Workaround does work.