Import Module with an different user account - powershell

Are you able to import a module through PowerShell with a different user account? I am specifically attempting to import the ActiveDirectory module with a different account to the currently logged in one.
I don't want to go all out for the console though because I am attempting to use the current Outlook process to send an email after the part of the code is done, and if the entire console is elevated it will give a COM error (instance of PowerShell and Outlook are not elevated together).
The SMTP way of sending an email or through Send-Mail won't work as even though I can ping the SMTP server, I get the below error message, which from what I've read is because I am unable to communicate with the SMTP server appropriately?
Exception calling "Send" with "1" argument(s): "Failure sending mail."
At C:\Users\\Desktop\SCRIPT.ps1:64 char:9
+ $SMTP.Send($MSG)
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : SmtpException

You can't import a module with a different account as it doesn't work this way. You need to run the individual commands themselves with alternative credentials.
As you mentioned AD I've used Get-ADUser as an example but a lot of powershell commands have a Credential or PSCredential parameter of some kind, check the documentation to find out.
$Credentials = Get-Credential
Get-ADUser JohnSmith -Properties DistinguishedName -Credential $Credentials
This above example will prompt for credentials, but you can also save them in the script instead on entering them every time.
NOTE: Saving credentials in a file isn't secure so be careful what credentials you save and where you store them!
$Username = "DomainUserName"
$Password = "PlainPassword" | ConvertTo-SecureString -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($Username ,$Password)
There are also other ways to save credentials, but that's too much to go into here.

The AD module for powershell is a wrapper around much of the .NET framework's System.DirectoryServices namespace of code.
.NET in turn is wrapped on top of the older COM ADSI component.
Because of this, it is possible to use windows cached credentials to handle the AD work without using the -Credential option.
If you cache a Windows Domain credential prior to running the script, the AD cmdlets will use those cached credentials to authenticate to the DC. Of course, there's no requirement to remove the cached credential...but realize it's static. if the password changes in the domain, you need to re-cache the cred.
The management of domain creds can be done by command line as well using the cmdkey.exe program that is present since Win7. Using this command line tool, you could set the windows credential just before you run your script, then remove the credential after.
Note that the use of the cached creds is based solely on the server name that the cmdlet will attempt to communicate. If you are not specifying a DC in your cmdlet calls, then it will use the %logonserver% environment variable.
The critical piece then is that the servername used by ADSI must match exactly in the credential cache. If the short name (server01) is used, then that must be in the cache. If the full dns name is used (server01.domain.com), then that must be in the cache. If you feel that your script may change to another server, then that server must be in the cache.

Related

Powershell JEA Security hole with commands embedded in functions?

This seems bonkers so I'm hoping I didn't find a big security gap... I have Powershell JEA (just enough administration) successfully set up on a server to allow only certain administrative functions. Specifically, I don't have the "net" command allowed at all. If I do the below:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
net stop "My windows service"
}
Then I get the error below as expected:
The term 'net.exe' 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.
+ CategoryInfo : ObjectNotFound: (net.exe:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
BUT, if I wrap my "net.exe" usage inside a function, it actually works:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
function StopService($servicename) {
net stop "$($servicename)"
}
StopService "My windows service"
}
The above does not throw an error and actually stops the service. WTF?
This is more than the "net" command. Another example: "Out-File" is not allowed. The below code fails:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
"hacked you" | Out-File C:\test.txt
}
With the error:
The term 'Out-File' 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.
+ CategoryInfo : ObjectNotFound: (Out-File:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : vxcazdev01
But if I do it this way, it works:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
function DoIt() {
"hacked you" | Out-File C:\test.txt
}
DoIt
}
Why is this happening? Am I missing something? The JEA project on github is now read-only so I can't open an issue there.
Edit to add: the same problem happens if I use Enter-PSSession instead of Invoke-Command.
Edit to add relevant session config pieces: My session config file only has a few customizations from the default file produced by the New-PSSessionConfigurationFile command:
SessionType = 'Default'
RunAsVirtualAccount = $true
RoleDefinitions = #{
'MYDOMAIN\MYADGROUP' = #{ RoleCapabilities = 'MyCustomRole' }
}
MYADGROUP is the only group my test user is a member of. And then this is registered on the server like so:
Register-PSSessionConfiguration -Path "C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\VSM.pssc" -Name 'MYCONFIG' -Force
I'm just going to answer this question myself with the answer: security hole by design.
The documentation says this:
The body (script block) of custom functions runs in the default language mode for the system and isn't subject to JEA's language constraints.
It's rather surprising, to me at least, that JEA will let you lock down actions on a server in security sandbox, but as soon as one writes their own custom functions they have full administrative rights to the machine and have broken out of the box. Allowing or restricting the creation of custom functions via the language mode is one thing, but bypassing the set security permissions is another. In my opinion, user-written custom functions should be subject to full security limitations; custom functions written in the role capabilities files should have full admin rights as the documentation indicates.
The other answer by HAL9256 is great, but it describes the benefits of JEA, which is not the topic of this post.
I wouldn't say that it's a security hole, it's that you are clearly demonstrating what could happen on a system when you have not set up a fully secured configuration. Microsoft even states JEA doesn't protect against admins because "they could simply RDP in and change the configuration". We need the correct combination of SessionType and RoleDefinitions, and that they are meant for two different configurations.
Your example demonstrates a configuration setup where, even though we lock the front door of the house, we started off with a house that had all the windows and doors open. It is fully possible to get in through the back door, or reach through a window and unlock the front door, thus demonstrating the fruitlessness of locking the front door. For example, I don't need to run net stop I could just do a taskkill instead, or..., or..., etc.
Let's look at the overview of what JEA is designed for:
Reduce the number of administrators on your machines using virtual accounts or group-managed service accounts to perform privileged actions on behalf of regular users.
Limit what users can do by specifying which cmdlets, functions, and external commands they can run.
Better understand what your users are doing with transcripts and logs that show you exactly which commands a user executed during their session.
Reduce the number of administrators
We can use JEA to remove people from the local administrators group, or larger Domain Admin groups. They can then selectively get elevated Administrator rights when needed through Virtual Accounts.
If we set it up with the SessionType = 'Default' this enables all language features. We essentially can have a Jr. Technical Analyst without Domain Admin rights, without Local Admin rights, log on, and do Administrative duties. This is what the session type is meant for.
Limit what users can do
If we set it up with the SessionType = 'Default' this enables all language features. In mode it doesn't matter what commands we limit, all the doors and windows are open and we can pretty much do whatever we want. One rule in Windows is that there is always 3-4 different ways to do something. You just can't plug all the holes when everything is wide open.
#MathiasR.Jessen is right, the only way to Limit what users can do is to first lock down the system. Setting SessionType = 'RestrictedRemoteServer' locks down the session to:
Sessions of this type operate in NoLanguage mode and only have access
to the following default commands (and aliases):
Clear-Host (cls, clear)
Exit-PSSession (exsn, exit)
Get-Command (gcm)
Get-FormatData
Get-Help
Measure-Object (measure)
Out-Default
Select-Object (select)
No PowerShell providers are available, nor are any external programs
(executables or scripts).
This starts us out with a completely locked up house. We then selectively enable the needed commands. Ideally we should pre-create custom functions so that the custom function is the only thing they can run, they are technically not even allowed to execute the commands inside the function at all, it's all handled by the Virtual Account.
What you did was essentially exploiting this custom function capability, by "cheating" and creating our own "custom function" that will run in the Virtual Account scope, and not your own, which is why it was able to run "non-allowed" functions, and you were not. If the SessionType = 'RestrictedRemoteServer', you wouldn't be able to create scripts or custom functions like demonstrated, and hence, the "hole" would not be there.
Better understand what your users are doing
Finally the other benefit for JEA is that it can record a transcript of all the commands that are run. This might be needed for audit reasons or fed into a SIEM solution or to find out how your Jr. Technical Analyst messed up your system ;-).

Using System.Management.Automation and an exported session, how do I specify credentials for an O365 call?

I'm building a small console app that uses constructs in the System.Management.Automation namespace to connect to ExchangeOnline and perform various tasks. The overhead time of creating and importing a new session with each run during my dev & test is prohibitive.
Thus, I've elected to save the session to disk using Export-PSSession. This all works fine from a PowerShell prompt, like so:
Import-Module ExchangeOnline
Get-Mailbox
I'm prompted for my credentials, and off we go.
Unfortunately, the same can't be said for running the same sequence under Automation:
System.Management.Automation.MethodInvocationException: Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "PromptForCredential" with "4" argument(s): "A command that prompts the user failed because the host program or the command type does not support user interaction. The host was attempting to request confirmation with the following message: Enter your credentials for https://outlook.office365.com/powershell-liveid/.""
How do I send my credentials to O365 when using System.Management.Automation?
This Q&A almost answers it, but not quite.
Here's my code.
Implementation
Friend Class Monad
Implements IDisposable
Public Sub New()
Me.SessionState = InitialSessionState.CreateDefault
Me.Monad = PowerShell.Create
End Sub
Public Sub ImportModule(Modules As String())
If Me.RunSpace.IsNotNothing Then
Me.RunSpace.Dispose()
Me.RunSpace = Nothing
End If
Me.SessionState.ImportPSModule(Modules)
Me.RunSpace = RunspaceFactory.CreateRunspace(Me.SessionState)
Me.RunSpace.Open()
Me.Invoker = New RunspaceInvoke(Me.RunSpace)
End Sub
Public Function ExecuteScript(Script As String) As Collection(Of PSObject)
Dim oErrors As Collection(Of ErrorRecord)
ExecuteScript = Me.Invoker.Invoke(Script)
oErrors = Me.Monad.Streams.Error.ReadAll
If oErrors.Count > 0 Then
Throw New PowerShellException(oErrors)
End If
End Function
Protected Overridable Sub Dispose(IsDisposing As Boolean)
If Not Me.IsDisposed Then
If IsDisposing Then
If Me.RunSpace.IsNotNothing Then Me.RunSpace.Dispose()
If Me.Invoker.IsNotNothing Then Me.Invoker.Dispose()
If Me.Monad.IsNotNothing Then Me.Monad.Dispose()
Me.RunSpace = Nothing
Me.Invoker = Nothing
Me.Monad = Nothing
End If
End If
Me.IsDisposed = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Me.Dispose(True)
End Sub
Private ReadOnly SessionState As InitialSessionState
Private IsDisposed As Boolean
Private RunSpace As Runspace
Private Invoker As RunspaceInvoke
Private Monad As PowerShell
End Class
Call
Friend Function GetMailbox() As IEnumerable(Of PSObject)
Using oMonad As New Monad
oMonad.ImportModule({"ExchangeOnline"})
Return oMonad.ExecuteScript("Get-Mailbox")
End Using
End Function
The ExchangeOnline module has some issues with that. It wants to be able to display an interactive modern authentication dialog, and there's no reliable way to stop it. You can feed it credentials, but it will barf if it needs to display an interactive dialog (as it does for MFA).
For storing and retrieving the credentials, you can use ConvertFrom-SecureString and Export-Csv or Export-CliXml as in the answer from InteXX, but that will stop working if basic authentication is disabled for the account you're using, or after 13 October 2020 when basic auth is disabled in exchange online (see KB4521831 ref https://support.microsoft.com/en-us/help/4521831/exchange-online-deprecating-basic-auth). Until then, you can also use a module like VaultCredential to manage credentials (note that it won't work across accounts).
So the next question is probably how you get a token for modern auth and how you present it to Exchange Online to authenticate with powershell. That's not hard at all.
You can bang against the endpoints with .NET web calls without too much effort, but it's easier to use one of the officially sanctioned libraries like ADAL.PS (deprecated) and MSAL.PS. Go ahead and Install-Module -Name MSAL.PS from the powershell gallery. Once it's there (and run Get-Module -Refresh -ListAvailable to make sure it can autoload) you can run:
$Token = Get-MsalToken -ClientId "a0c73c16-a7e3-4564-9a95-2bdf47383716" -RedirectUri "urn:ietf:wg:oauth:2.0:oob" -Scopes "https://outlook.office365.com/AdminApi.AccessAsUser.All","https://outlook.office365.com/FfoPowerShell.AccessAsUser.All","https://outlook.office365.com/RemotePowerShell.AccessAsUser.All"
You can also add "-LoginHint " (i.e. probably your email address) to skip the initial locator prompt, and it might help to add "-TenantId '00000000-0000-0000-0000-000000000000'" (except use the actual GUID for your Azure AD tenant).
Once a token is acquired, MSAL will keep it in its cache and automatically refresh it for you. This might make non-interactive use a little more difficult. You can review preferred ways to deal with it (https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens), or work with the libraries or interfaces at a lower level (such as using AcquireTokenSilentAsync()).
To actually use the token against Exchange Online, you need to use basic auth. It will still work even after they deprecate it, but it won't validate your account's password, it will only do other stuff like accept encoded tokens. Basically you need to Get-PsSession -Credential $EncodedBasicCredential where $EncodedBasicCredential is constructed with your UPN as the username and the Base64-encoded authorization header value as the password. For example:
$EncodedBasicCredential = [System.Management.Automation.PSCredential]::new($Token.account.username,(ConvertTo-SecureString -AsPlainText -Force -String ($Token.CreateAuthorizationHeader())))
Note that $Token.CreateAuthorizationHeader() just takes the value of $Token.AccessToken
and prepends it with "Bearer ". Now all you need to do is create a New-PsSession with the appropriate ConnectionUri, ConfigurationName, and your credential object:
$ExchangeOnlineSession = New-PSSession -Name ExchangeOnline -ConnectionUri "https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true" -ConfigurationName Microsoft.Exchange -Credential $EncodedBasicCredential -Authentication Basic
And you can import that to the parent session as you like:
$ExchangeOnlineModule = Import-PSSession -Session ($ExchangeOnlineSession) -WarningAction Ignore
To answer your specific question (how you specify credentials), it all depends on how you access your identity authority. If you are using ADFS on premises and your script runs on premises, then you should be able to run the process as the desired identity and Get-MsalToken will automatically use integrated windows authentication against ADFS without prompting you. If you are using PTA or native auth directly against Azure AD, then you'll need to look at creating a client application and using a secret or a certificate to authenticate against Azure AD to get your token.
I think this will be reasonably easy to translate from powershell to C#, but I'm a scripting sys admin, not a coder.
I was able to accomplish this by editing the exported module.
First, export your O365 credentials:
Get-Credential | Export-Clixml -Path D:\Modules\O365Credential.xml
With that XML file now in hand, open the module for editing in your preferred IDE
Search for the function call PromptForCredential
Note that the call is a part of a larger call to the Set-PSImplicitRemotingSession cmdlet
Comment out that entire outer cmdlet call, e.g.:
Insert an uncommented copy of the call (optional: fix the broken indentation)
For the -Credential parameter, replace the value with $Credential
Above the new Set-PSImplicitRemotingSession call, add this line:
$Credential = Import-Clixml -Path D:\Modules\O365Credential.xml
Your finished code should look something like this:
Save the module
To test, open a new PowerShell prompt and import the updated module. Run Get-Mailbox. You should receive a list of the mailboxes hosted by your O365 tenant, without being prompted for credentials.
Note that the module uses and decrements a RunSpace connection against your quota. You'll want to close that connection gracefully when you're done with your session:
Get-PSSession | Where-Object { $_.ConfigurationName -eq 'Microsoft.Exchange' } | Remove-PSSession -ErrorAction SilentlyContinue
Now you can import and use the module under the System.Management.Automation namespace, as shown in the VB.NET code above.

Is there a way to change the service account from "This account" to "Log in as" using Powershell?

New to powershell but bear with me. I am trying to automate an install of Prosystem Fx Engagement, but need to install a SQL instance. I have already done this part, but I will also need to automate the changing of a services account within services.msc. It will need to be changed from "This account" to "Log in as Local System Account"
https://imgur.com/en9COWl
The name of the service is MSSQL$PROFXENGAGEMENT, and the display name of the service is SQL Server (PROFXENGAGEMENT).
I don't really want to use the method below because the password is visible on the .ps file. I tried looking around but was not able to find anything. Is this even possible?
$LocalSrv = Get-WmiObject Win32_service -filter "name='MSSQL$PROFXENGAGEMENT'"
$LocalSrv.Change($null,$null,$null,$null,$null,$false,"DOMAIN\administrator","PASSWORD")
As for the method you are saying you don't want to use for the account change activity. You don't have to and should not put plain text password in scripts.
You can prompt for the password, store that in a variable and use it. Yet, that means a human has to be there to address that, so, not very automated.
So, this means you need to set the credentials in a file or other store and call that from your script.
Meaning like what is described below. This of course has to be done in advance of any other use case that will need it.
using secure password with multiple users without prompt
#saving credentials
Get-Credential | Export-CliXml -Path c:\credential.xml
#importing credentials to a variable
$Credential = Import-CliXml -Path c:\credential.xml
A YouTube Video on the topic:
Learn to securely use Passwords with PowerShell
You could also use the Windows Credential Store, and call it from there. Also shown in the above video.
Using Windows Credential Manager
https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Credentials-d44c3cde
https://www.powershellgallery.com/packages/CredentialManager/1.0
https://www.experts-exchange.com/questions/29061982/Powershell-Using-credentials-stored-in-Credential-Manager.html

Constrained endpoint on specific user

I'm trying to create a PowerShell endpoint constraint that restricts an user to only execute the functions in a custom module I made.
The first thing I did is import-module mymodule.psm1 which allows me to run my modules fine withing my host system.
Then the following PS command creates the configuration file for the endpoint which allows the functions inside the brackets to be the only functions the user gets to execute.
New-PSSessionConfigurationFile -VisibleFunctions('Get-Command','Get-Info', 'CreateAD-User','Generate-Html','Change-Logon') -LanguageMode ‘ConstrainedLanguage’ –SessionType ‘RestrictedRemoteServer’ –Path ‘c:\test\helpdesk.pssc’
Then I register the endpoint with
Register-PSSessionConfiguration –Name ‘HelpDesk’ -ShowSecurityDescriptorUI –Path ‘c:\test\helpdesk.pssc’
and selected which user I want allow to have these constrains once the SecurityDescriptorUI pops up. Once I log into the user that I set up the constrains for with
Enter-PSSession -computername SRV1-AD -Credential $credential -ConfigurationName HelpDesk
These are the allowed cmdlets / functions that the user is allowed to execute. These are the default required cmdlets to allow remote connections into a system.
How can I allow my custom module to be the only functions the endpoint allows users to execute? or How can I import my module into configuration file so it executes every time the HelpDesk end point configuration is used. I know that in the configuration file there's a line to import modules but Import-Module is not actually a module an example of a module would be ActiveDirectory, if I'm able to find what module import-module is a part of I think I should be able to do a quick and dirty work around for this.
UPDATE
A dirty solution I found for this was to enter into the user's session and disable all cmdlets / functions except the ones I want to allowed for example import-module & Get-Command with import-module I can manually import my custom module and my functions will be the only ones visible to user. But this is not a perfect solution because this means that I would need to download my module into every system I want this to take effect and it's no longer a one to many solution. The ideal solution is to have my module locally stored, enter into a session with the registered end point and have my module already imported into the users account.
Enter-PSSession -computername SRV1-AD -Credential $credential -ConfigurationName HelpDesk
Further Update
User #prasoon-karunan-v suggested I used -ScriptsToProcess & FunctionDefinitions to import the module so I used the following command
New-PSSessionConfigurationFile -VisibleFunctions('Get-Command','Get-Info', 'CreateAD-User','Generate-Html','Change-Logon') -LanguageMode ‘ConstrainedLanguage’ –SessionType ‘RestrictedRemoteServer’ –Path ‘.\EndPoint.pssc’ -ScriptsToProcess C:\Users\Administrator\Desktop\Modules\ImportM.psm1
In the configuration file I also set the functions I want to use like so
# Functions defined in this session configuration
FunctionDefinitions = 'Get-Command','Get-Info', 'CreateAD-User','Generate-Html','Change-Logon'
When I tried to establish a session it would throw the following error
Then I thought maybe it's not working because were not telling the command to import anything were just pointing to the module file, so maybe I need to create a small script that imports the module then add it the configuration file. So that's exactly what I did I created a small script with just,
import-module C:\Modules\ImportM.psm1 and then I went over to the .pssc
file and added this script to the ScriptsToProcess but I get the following error after I try to establish a session to the constrained endpoint.
Language Mode is set to
LanguageMode = 'RestrictedLanguage'
use -ScriptsToProcess parameter, which can be used to import your custom module.
See below as well.
Get-Help New-PSSessionConfigurationFile -Parameter ScriptsToProcess
Get-Help New-PSSessionConfigurationFile -Parameter FunctionDefinitions
Update:
Be sure about the language mode to use,
see here

Creating file in a user context in powershell

I am trying to create a file using powershell in a specific user context. E.g I have a user user01 on my local machine and I want to create a file in its context.
I am doing something like
New-Item c:\file.txt -Credential User01
It works but prompts me for password which I dont want it to. Is there any way I can accomplish this without having it prompt for password ?
The credential parameter on new-item is not actually supported for filesystems, so I'm not sure what you mean by "it works." It does NOT create the file as the passed user. In fact, the filesystem provider will say:
"The provider does not support the use of credentials. Perform the operation again without specifying credentials."
Taking an educated guess, I'd say you're trying to create a file with a different owner. PowerShell cannot do this on its own, so you'll need the following non-trivial script:
http://cosmoskey.blogspot.com/2010/07/setting-owner-on-acl-in-powershell.html
It works by enabling the SeBackup privilege for your security token (but you must already be an administrator.) This allows you to set any arbitrary owner on a file. Normally you can only change owner to administrators or your own account.
Oh, and this script is for powershell 2.0 only.
Rather than use a PowerShell cmdlet or .NET scripting on this one, you might take a look at the Windows utility takeown.exe. However, even it requires you supply the user's password that you're assigning ownership to.
Ok, I do start process in the user context and then create a file. Works like a charm.
Password, FilePath and UserName are passed in as arguments from command line.
$pw = convertto-securestring "$Password" -asplaintext –force
$credential = new-object -typename system.management.automation.pscredential -argumentlist "-default-",$pw
$localArgs = "/c echo>$FilePath"
[System.Diagnostics.Process]::Start("cmd", $localArgs, "$UserName", $credential.Password, "$Computer")
Or just make a call to SUBINACL.EXE? No need for password then.