Dynamically create a cmdlets/module in PowerShell - powershell

This is what I want to achieve and I can't find anything about this.
I want to create a remote module proxy, for a module that is available on a remote server.
I know how to work with remoting but I want something that creates cleaner script files.
To give an example. If I want to execute MyCmdlet from module MyModule on a remote computer I would do this
$block={
# Invoke the cmdlet from a module named MyCmdlet
MyCmdlet -Parameter1 -Parameter2
}
Invoke-Command -ComputerName "" -ScriptBlock $block
But I would like to land into something like this
Import-Module MyModuleRemote
MyCmdlet -ComputerName "" -Parameter1 -Parameter2
Please noticed that MyModule is not installed on my client machine.
I could re-write the module with Invoke-Command wrapper for each cmdlet but that is not the purpose. What I would like to do is remot-ify the MyModule by creating an proxy equal proxy per cmdlet and parameter. Even the Get-Help should work at least for the parameter composition.
I have a couple of ideas but I'm not sure if it is even possible.
Create a powershell module e.g. PSRemotify that will probe the module on the remote server and generate the code.
If I chose to write files to the file system then this should be possible, if I could do reflection on the cmdlets.
If I don't want to save files then I need to do everything in memory. Can I write a cmdlet's body in memory? Can I generate a string and import its embedded cmdlet?
Create a script that does 1.2.
My preference would be option 1.2. Very clean and without leaving any traces on the file system.
Any ideas? Has anybody tried something like already?
Conclusion after my investigation and answer from #Persistent13:
PowerShell offer this feature out of the box. it is known as IMPLICIT REMOTING. Before #Persistent13's answer I took the wrong part because I think it is interesting to share my experience, I've blogged about it.
import and use module from a remote server

It sounds like what your looking for is implicit remoting and is fairly simple to set up.
Please note I've taken the instructions for this from here.
To create an implicit session you would:
PS C:\> $foo = New-PSSession -ComputerName DC1
PS C:\> Import-Module -PSSession $foo -Name ActiveDirectory
PS C:\> Get-ADUser
The above would open a PowerShell session on the computer DC1, import the ActiveDirectory module on the remote computer, and the command is run against the remote computer while it appears to be executed locally.
It is also possible the prefix the implicitly imported modules in case a local module would conflict with the remote one.
PS C:\> $foo = New-PSSession -ComputerName DC1
PS C:\> Import-Module -PSSession $foo -Name ActiveDirectory -Prefix DC1
PS C:\> Get-DC1ADUser
The advantage of this is there is no need to remot-ify your module and so long as it is present on the remote computer and PowerShell remoting is allowed.
However one caveat of this method is that the type of the object returned will change.
Deserialized.Microsoft.ActiveDirectory.Management.ADUser when implicit remoting is used.
Microsoft.ActiveDirectory.Management.ADUser when run locally.

When I raised the question back then, I didn't know the concept of implicit remoting.
In practice you import a module using Import-Module but you also specify a session to a remote server with the -Session parameter.
For example to import Microsoft.PowerShell.Management from server $target="SERVER" execute this
$session=New-PSSession -ComputerName $target
Import-Module Microsoft.PowerShell.Management -Session $session
With this a proxy module is generated in the current session that offers the same cmdlets as the one specified. The proxy cmdlets automatically push the parameters to the remote session $session and capture its output. With Microsoft.PowerShell.Management implicitly imported from SERVER the Get-Service returns the services from SERVER.
You can really see the implementation of the proxy cmdlets by executing
$module=Get-Module Microsoft.PowerShell.Management
$module.Definition
It's at least interesting.

Related

Updating SSL bindings for IIS with Powershell 7

I'm trying to update a PS5 script to PS7, mainly because the script does work that requires a PS Core module.
Part of the script involved updating IIS bindings to use a different SSL Certificate. The cert is in the store and ready to be used - I just need to change the thumbprint on the binding.
My PS5 script used Get-WebConfiguration to get the bindings and then just looped through, calling RebindSslCertificate on relevant bindings.
I've tried using Set-WebConfigurationProperty and Set-WebBinding; neither errors but neither actually updates the binding with IIS - example:
Set-WebConfigurationProperty -Name 'certificateHash' -Value $newCert.Thumbprint -PSPath "IIS:\\" `
-Filter "/system.applicationHost/sites/site/bindings/binding[#protocol='https'][#bindingInformation='*:443:hostname']" `
Could anyone help point me in the right direction for what I'm missing?
Thanks,
Mark.
P.S., Apologies if this is a repeat question but all I can find is old stuff that doesn't work or relates to "-Set-Item IIS:\SslBindings" Maybe there is someway to get the IIS drive working with remoting?
Ran into this on 9/10/2021 using Powershell 7.1.4.
As of date of writing, this is an open issue on github for PowerShell.
Link for reference: https://github.com/PowerShell/PowerShellModuleCoverage/issues/14
Issue is that PowerShell 7 is based on .NET Core and the PS module WebAdministrator is based on .NET Framework.
When you run
Import-Module WebAdministration
WARNING: Module WebAdministration is loaded in Windows PowerShell using WinPSCompatSession remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell please use 'Import-Module -SkipEditionCheck' syntax.
Notice the mention of 'WinPSCompatSession' in the warning. If the module manifest doesn't indicate that the module is compatible with PowerShell Core, then it gets loaded via the Windows PowerShell Compatibility Feature.
It seems this module partially works in compatibility mode, however if you try to work with IIS:\ then you start getting errors.
Alternatively, if you run the parameter in the warning you get this.
Import-Module -SkipEditionCheck WebAdministration
Import-Module: Could not load type 'System.Management.Automation.PSSnapIn' from assembly 'System.Management.Automation, Version=7.1.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
A quick test in PowerShell 7.1.4 will show you that you can't access the IIS connector.
PS C:\Windows\System32> Import-Module WebAdministration
WARNING: Module WebAdministration is loaded in Windows PowerShell using WinPSCompatSession remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell please use 'Import-Module -SkipEditionCheck' syntax.
PS C:\Windows\System32> cd IIS:\
Set-Location: Cannot find drive. A drive with the name 'IIS' does not exist.
However, if you open up PowerShell 6 you can do this no problem.
PS C:\WINDOWS\system32> Import-Module WebAdministration
PS C:\WINDOWS\system32> cd IIS:\
PS IIS:\> dir
Name
----
AppPools
Sites
SslBindings
My next step is trying to get this to work by loading the .NET assembly directly. Will update with the solution
[System.Reflection.Assembly]::LoadFrom("$env:systemroot\system32\inetsrv\Microsoft.Web.Administration.dll")

PowerShell Invoke-Command with FilePath on local computer - vague parameters error?

I want to run a script in file on the local machine using Invoke-Command so I can pass in parameters with -ArgumentList. I've been getting an error I don't understand, so I simplified my command. When I do this:
Invoke-Command -FilePath 'getprocess.ps1'
The content of getprocess.ps1 is:
Get-Process
The error message I get is:
Invoke-Command : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ Invoke-Command -FilePath 'getprocess.ps1'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Command], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.InvokeCommandCommand
I'm baffled by this error message. What does it mean? How do I get this to work?
tl;dr:
Generally, do not use Invoke-Command for local invocations - while technically possible, there's only one specific use case where doing so is called for (see below).
Instead, invoke scripts directly:
.\getprocess.ps1
Note: Unlike cmd.exe, PowerShell by design requires .\ in order to execute an executable located in the current directory. That is, to avoid accidental execution of executables in the current directory rather than from a directory listed in $env:Path, PowerShell, as a security feature, requires you to signal the intent to execute something in the current directory (.) explicitly.
For script blocks ({ ... }), use &, the call operator (e.g., & { Get-Date }).
For syntactic reasons alone, you situationally also need & for script-file paths if they're specified either as a quoted path (e.g., & '.\getprocess.ps1') and/or if the path involves variable references (e.g.,
& $HOME\getprocess.ps1).
(Separately, . , the dot-sourcing operator is needed in both cases in order to execute a script [block] directly in the caller's scope rather in a child scope).
Note that you can technically combine passing a script block to Invoke-Command (parameter -ScriptBlock) with invoking a local script:
# The script block positionally binds to the -ScriptBlock parameter.
# This is essentially the more expensive equivalent of:
# & .\getprocess.ps1
Invoke-Command { .\getprocess.ps1 }
This is slower and offers no advantage over direct invocation.
However, there is one conceivable use case:
If the script isn't an advanced script and you wanted to take advantage of Invoke-Command's stream-output-collecting
common parameters, such as -ErrorVariable (if the script or function being invoked is advanced, it supports these common parameters itself).
# Invoke locally and collect errors in $errs
Invoke-Command { .\getprocess.ps1 } -ErrorVariable errs
Caveat: At least as of PowerShell 7.2, Invoke-Command does not apply the common -ErrorAction parameter to errors that occur in the script block, so it cannot be used to control error handling; e.g., -ErrorAction Stop has no effect on the commands in the script block.
As for what you tried:
Indeed, as you point out in your own answer, -FilePath must be combined with the
-ComputerName parameter (that the error message is so generic is unfortunate).
More generally, -FilePath must be combined with any of the parameters that request remote execution, which includes -Session, -ConnectionUri, -VmId / -VmName, and, on Unix-like platforms, -HostName, and -SSHConnection.
The purpose of parameter -FilePath is to copy the content of a local script (*.ps1 file) to a remote computer for execution there. That is, it is a convenient mechanism of executing the code of a script that is (only) available locally on a remote machine.
While you can technically target the local computer via -ComputerName localhost (or, more succinctly, via -ComputerName . / -cn .), this does not amount to a local call:
Whenever -ComputerName is specified - even with -ComputerName localhost - PowerShell's remoting infrastructure is used, which has major implications:
The target computer - even if it is the local one - must be set up for PowerShell remoting - see about_Remote_Requirements.
If you target the local machine specifically, you must be running in an elevated session (running as administrator).
Execution will be much slower than direct (local) invocation.
Type fidelity can be lost for both input and output data, given that cross-process marshaling via PowerShell's XML-based serialization infrastructure is involved - see this answer.
That said, if the intent is to locally test remote execution of your script, and your local machine is set up as a remoting target, then use of -ComputerName localhost (-ComputerName . / -cn .) makes perfect sense, given that PowerShell's remoting infrastructure is then involved in the same way it would be in a truly remote call.
Note, however, that such "loopback remoting" calls require elevation (running as admin).
Although the error message doesn't make it clear, the -FilePath parameter makes the -ComputerName parameter required. To explicitly target the local computer, use -ComputerName localhost.
Invoke-Command -FilePath 'getprocess.ps1' -ComputerName localhost
I can't really use invoke-command locally. You use that when you're trying to run a command on a remote PC.
For example, you would want to run something like:
invoke-Command -ComputerName REMOTE-PC -Credentials $credential -Scriptblock {Get-Process}
That error is basically telling you that you need to fill out more parameters that are tied to that command.
try running Get-Help Invoke-Command to see some info on the command and how to run it.
You must have a computername.
$parameters = #{
ComputerName = '255.255.255.255'
FilePath = 'getprocess.ps1'
Credential = 'Domain01\User01'
}
invoke-command #parameters
Use your IP :) To allow that you must also include -credential
If that doesn't do it... invoke-expression may be a semi-suitable replacement for testing until you are ready to invoke-command on the remote machine.
You could run it locally like this, but you'd have to be at the administrator (elevated) prompt. It's nice to be able to run it as a test.
invoke-command localhost getprocess.ps1
You can actually do a strange form of parallelism too:
invoke-command localhost,localhost,localhost getprocess.ps1

PowerShell: How to run a powershell script on a remote machine using WMI

To be specific: I want to run a powershell script on a remote windows server, but I can connect to this server using WMI only.
I used, for example, Get-Wmiobject to get some data like the running processes, but I failed after a lot of searching, to find a way to run a powershell script block on this remote one. One of the commands that I found is Invoke-Command but this one uses the winRM which is not opened to that remote server.
So, it is NOT allowed to run a powershell script on a remote server using WMI? I didn't find a clear and a direct answer for that.
tl;dr
Consider using psexec as an alternative to PowerShell remoting for executing arbitrary commands.
The list of PowerShell commands that support targeting remote machines without relying on PowerShell remoting is limited (see below); they may all be WMI-based (I'm not sure), and they're focused on retrieving and manipulating remote resources (as WMI is in general) rather than providing the ability to execute arbitrary commands.
Update: Alberto Varga's helpful answer points out that the Win32_Process WMI class's .Create method indeed does allow creation of arbitrary processes;
the documentation of PowerShell's Invoke-WmiMethod cmdlet even contains an example.
By contrast, Invoke-Command, which does offer the ability to execute arbitrary commands, does use PowerShell remoting, as you've discovered, which requires the WS-Management protocol, as implemented by Microsoft's WinRM service, among other prerequisites -
see Get-Help about_Remote_Requirements.
The most generic of the non-remoting commands listed below is Invoke-WmiMethod, which provides open-ended access to WMI classes and their methods.
Note, however, that Microsoft recommends using the more recent *-Cim* cmdlets such as Invoke-CimMethod in the interest of cross-platform support, and that these CIM-compliant cmdlets again rely on WS-Management (WSMan) standards, as PowerShell remoting does.
List of PowerShell cmdlets that support targeting remote machines via -ComputerName without using PowerShell remoting, as of PSv5.1 (see Get-Help about_Remote_FAQ for background info):
Add-Computer
Clear-EventLog
Get-EventLog
Get-HotFix
Get-Process
Get-Service
Get-WmiObject
Invoke-WmiMethod
Limit-EventLog
New-EventLog
Register-WmiEvent
Remove-Computer
Remove-EventLog
Remove-WmiObject
Rename-Computer
Restart-Computer
Set-Service
Set-WmiInstance
Show-EventLog
Stop-Computer
Test-Connection
Write-EventLog
This can be easily done. What you want is Win32_Process and method called Create. This allows you to spawn processes on remote machines 2K3 and higher.

Powershell implicit remoting called from another module file

We have a number of Powershell scripts that call "modules" with a load of reusable functions in. One function we have in a module sets up implicit remoting to our main DC and then imports the active directory module to the local session. I know this works as I have pasted the function into Powershell and it works. But when a script loads the module that contains the function it imports correctly but the cmdlets are not useable from the main script. I imagine this is something to do with scoping but I just can't see how to get this to work. For reference we cannout change the idea of calling modules with reusable functions in as there to many scripts that rely on it and it would be a big job to change it.
--Added for clarification from a comment below
I have a script, lets call it Script1, this script does some stuff but also loads some additional functions from other script modules using the following code
$Global:ICTSGVModuleLocation="\\server1\Scripts\Support Files\PS Modules\Global Modules"
get-childitem $Script:ICTSGVModuleLocation | foreach-object $_.name {Import-Module "$Global:ICTSGVModuleLocation\$_" -Global}
In one of the other script modules (lets call it script2) is some code to create an implicit remoting session and import an active directory module, code below
Function Global:ICTSGF.Adconnecter
{Try {$Script:connect = new-pssession -ComputerName "$Global:ICTSGVAdConnectorServer";
invoke-command -session $script:connect {import-module activedirectory -prefix cardiff};
Import-Module (Import-PSSession -session $Script:connect -module ActiveDirectory - AllowClobber) -Global}
Catch {ICTSGF.ScriptOutput "E2" "A4" "Loading Active Directory Module Failed"}
Finally {}}
In the main script (script1) i load the function by calling Global:ICTSGF.Adconnecter. This then loads the banner saying loading active Directory module and it appears to work. However when i attempt to run a command from the module imported it doesnt work
get-cardiffaduser $user
Thanks

powershell v2 remote features?

Just listened to Hansellminutes podcast. He had a talk with two Microsoft PS developers. They mentioned PS V2 remoting features.
I have some scripts based on PS v1. In terms of remoting commands or executions, I installed PS on local and a remote machines. Then I use PsExec.exe to push bat on remote to execute PS scripts. Now I am thinking to take advantage of PS V2.
To simple questions I have, to get a list of files on local, I can use the following codes:
$fs = Get-Item -Path $Path | Where { !$_.PSIsContainer ... } # more constrains in ...
if ( $fs -ne $null )
{
# continue to work on each file in the collection
...
}
What is the equivalent command to get a collection of files from a remote? I prefer to get a similar collection of file objects back so that I can access to their properties.
The second question is how to exec a command on remote with external application? I tried to use WIM Process before, but I could not get WMI class working on a case of Windows 2008 server. Then I used PsExec.exe to push a bat to a remote to execute PS script. It works in the cases. However, the problem I have to install PS on the remote as well. I am going to working another remote. I'll try to avoid to install PS on the remote. Can I take PS V2 advantage to execute a command on a remote Windows? What's the new commands?
By the way, normally, I have to pass user name and pwd to a remote. I guess in PS I have to pass user/pwd as well.
You can either put your code above in a script file and invoke it on a remote computer using V2 remoting like so:
PS> Invoke-Command remotePCName -file c:\myscript.ps1
You will need to be running with admin privs (elevated if UAC enabled) in order to use remoting. The command above will copy the script to the remote machine, execute it and return deserialized objects. These objects are essentially property bags. They are not "live" objects and setting properties on them like IsReadOnly will not affect the remote file. If you want to set properties then do it in your script that executes on the remote PC.
The option if you have a little bit of script is to use a scriptblock like so:
PS> Invoke-Command remotePCName { Get-Item C:\*.txt | Where {$_.IsReadOnly }
You can execute native commands (EXE) on the remote computer in either script or a scriptblock. You only need to make sure the EXE is available on the remote PC.
Regarding credentials, if you're on a domain and you have admin privs on the remote computer you won't need to pass credentials as your default credentials should work. If you need to run as a specific user then use the -Credential parameter on Invoke-Command like so:
PS> $cred = Get-Credential
PS> icm remotePCName { gci c:\windows\system32 -r *.sys } -credential $cred
Regarding your last comment, no PowerShell will use Windows integrated security so you should not have to pass any username or password unless you wanted to run it as a different user.
If you haven't yet enabled PS remoting, every time I've tried I've had to actually turn off UAC while I was enabling remoting (then I could re-enable UAC once remoting was enabled). Running Enable-PSRemoting from an elevated command prompt was not enough and the error message was not at all useful.
EDIT: I've just confirmed in a fresh Windows 7 VM that this is not an issue. It could have been a beta issue that I am no longer experiencing as I've been using beta/rc/ctp of PowerShell and Windows 7 for a long time.