Enter-PSSession to custom endpoint: Cmdlet not recognized - powershell

I am trying to setup an Endpoint-Server in my company and am struggling to connect to it. For testing I put a RcLogUtil Module in the Global Module Path
C:\windows\system32\WindowsPowershell\v1.0\Modules\RcLogUtil\
that exports the functions
'Out-LogToEventLog','New-LogMessage'
The Plan is to let a specific set of users access only those Logging-Functions.
I create a SessionConfiguration:
New-PSSessionConfigurationFile -Path C:\Scripts\LoggerEp.pssc `
-SessionType RestrictedRemoteServer `
-LanguageMode FullLanguage `
-ExecutionPolicy Unrestricted `
-ModulesToImport 'RcLogUtil' `
-VisibleFunctions 'Out-LogToEventLog' `
-VisibleCmdlets 'Split-Path'
Register it:
Register-PSSessionConfiguration -Path C:\Scripts\LoggerEp.pssc `
-Name loggerep `
-ShowSecurityDescriptorUI
And enter it on my local machine:
[W0216]> Enter-PSSession -ComputerName mka-ps-endpoint -ConfigurationName loggerep
Enter-PSSession : One or more errors occurred processing the module
'RcLogUtil' specified in the InitialSessionState object used to create
this runspace. See the ErrorRecords property for a complete list of
errors. The first error was: The term 'Split-Path' 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
+ Enter-PSSession -ComputerName mka-ps-endpoint -ConfigurationName loggerep
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Enter-PSSession], RunspaceOpenModuleLoadException
+ FullyQualifiedErrorId : ErrorLoadingModulesOnRunspaceOpen
The huge question now is.. why is the Session unable to find Split-Path? Or how do I tell the Endpoint to load that particular cmdlet?
I successfully tried the same with SessionType=’Default’ and it worked but with all the powershell clutter around it.
I would really apreciate any help I can get as I am stuck with this for quite some time now..
Thanks!

There is the option to disable each cmdlet in advance by using -SessionType Default with the -ScriptsToProcess 'C:\Scripts\LoggerEpStartup.ps1' Parameter when creating a SessionConfiguration.
New-PSSessionConfigurationFile -Path C:\Scripts\LoggerEp.pssc `
-SessionType Default `
-LanguageMode FullLanguage `
-ExecutionPolicy Unrestricted `
-ModulesToImport 'RcLogUtil' `
-VisibleFunctions 'Out-LogToEventLog' `
-ScriptsToProcess 'C:\Scripts\LoggerEpStartup.ps1'
C:\Scripts\LoggerEpStartup.ps1:
# Commands needed by PSSession (Also the commands used when
# creating a RestrictedRemoteServer )
$CmdsToExclude = #(
'Get-Command' , 'Out-Default' ,
'Exit-PSSession', 'Measure-Object',
'Select-Object' , 'Get-FormatData'
)
# Hide any other commandlets except the ones needed
# to create a remote session
Get-Command | Where Visibility -eq 'Public' | ForEach-Object {
if ( $_.Name -notin $CmdsToExclude ) {
$_.Visibility = 'Private'
}
}
But I want to avoid that aproach as it seems to be more of a clumsy workaround than a proper solution.

Related

When using a function within a Powershell module, a psm1, is it possible to export the ExchangeOnline functions to be accessible in the console?

Working on several Powershell functions which connect to Office365 services and automate many of my usual tasks. Found when I have the function in a psm1, running the function to connect to ExchangeOnline the functions are not exposed to the console and only functions within the same module.
While I know I can export-modulemember, this only works for functions when the module is loaded, happens well before I connect-office365 -exchangeonline.
Is there anyway to export the commands loaded when connecting to Office 365 after connecting?
By placing the following function in a psm1 instead of a ps1 and loading it, and then running connect-off365 -exchangeonline, all Exchange Online related commands are not usable in the console. Yet, having the same function in a ps1 and loading it, the ExchangeOnline functions work.
Function Connect-Office365 {
Param(
[Switch]$ExchangeOnline,
[Switch]$EO
)
if ($EO) { $ExchangeOnline = $true }
elseif ($EO -and $ExchangeOnline) { Write-Warning "No need to declare ExchangeOnline and EO" }
$Credential = Get-Credential -message "Enter Office 365 credentials,`r`nor cancel to not connect to Office 365"
if ($null -eq $Credential) { Write-Host "Skipped entering credentials"; $TryAgain = $False }
if ($ExchangeOnline) {
Write-Host "Connecting to Exchange Online Services"
$global:ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection -EA stop
Import-PSSession $global:ExchangeSession -AllowClobber -DisableNameChecking
}
}
For example
PS C:\Users\TechWithAHammer> import-module C:\scripts\Connect-Office365.psm1
PS C:\Users\TechWithAHammer> connect-office365 -eo Connecting to MSOL Services Connecting to Exchange Online Services
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 1.0 tmp_3zvdxnnc.gd0 {Add-AvailabilityAddressSpace, Add-DistributionGroupMember...
PS C:\Users\TechWithAHammer> get-mailbox
get-mailbox : The term 'get-mailbox' 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
+ get-mailbox
+ ~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (get-mailbox:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
As per the answer in Commands from implicit remoting module not available when created from another module's function, found that I had to use Import-Module to import the Exchange Online and Compliance and Security center modules into the global session.
Import-Module (Import-PSSession $global:ExchangeSession -AllowClobber -DisableNameChecking) -Global

How to Install Windows Updates on Remote Computer with PowerShell

I'm trying to install Windows Updates on a Remote Computer with this command:
$InstallSplat = #{
AcceptAll = $true
SendReport = $true
IgnoreReboot = if ($Reboot) { $false } else { $true }
PSWUSettings = #{
SmtpServer = "my mail server"
From = "myfrom <myfrom#myfrom.com>"
To = "myto <myto#myto.com>"
Port = 25
}
}
Invoke-Command -ComputerName $_ -Credential $cred -AsJob -ArgumentList $InstallSplat -ScriptBlock {
param([hashtable]$InstallSplat)
Import-Module PSWindowsUpdate
Install-WindowsUpdate #InstallSplat
$Error | out-file C:\install\installwinupdate.log -Append
}
I pass a credential Object with domain admin privileges in $cred but I still always get this error
Install-WindowsUpdate : Access denied (Ausnahme von HRESULT: 0x80070005 (E_ACCESSDENIED)) In Zeile:4 Zeichen:25
+ Install-WindowsUpdate #InstallSplat
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WindowsUpdate], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,PSWindowsUpdate.GetWindowsUpdate
The Command Install-WindowsUpdate itself does not have a credential parameter I could use. The Command needs to run in an elevated PowerShell, but I use an elevated PowerShell when starting this command on my Computer.
I Also tried creating a New-PSSession with my $cred and run Invoke-Command -Session $session instead of Invoke-Command -ComputerName $_ with the same result.
Does anybody know what's happening here? Why do I get Access denied?
It can't have anything to do with passing the $InstallSplat because the same thing happens if I don't pass any parameter at all and write the parameters and their Values directly at the command instead of splatting.
The Problem was, that you can't Download or Install Updates on a machine from another remote machine. Here's a list what you can or can't do remotely when it comes to Windows Updates
The solution is, to create a scheduled task on each server you want to install updates from a remote script, and start that task.
luckily, when you use the PSWindowsUpdate module, you don't have to do that yourself, you can just use Invoke-WUJob (formerly Invoke-WUInstall) which does the trick for you.
I used it like so ($ServerData.Value contains a list of my Servers) and it works like a charm. It creates a scheduled task on each server, and runs them immediately, if you add the -RunNow Parameter.
invoke-WUJob -ComputerName $ServerData.Value -Script { Import-Module PSWindowsUpdate ; Install-WindowsUpdate -AcceptAll -SendReport -IgnoreReboot -PSWUSettings #{From='xy';Port=25;SmtpServer='xy';To='xy'} | Out-File C:\install\PSWindowsUpdateLog.txt -Append} -Confirm:$false -verbose -RunNow
Note that what you specify as a script block in -Script will be pasted to -Command " <here> " in your scheduled task, so you should work with ' inside -Script.

Azure Runbooks - Missing PowerShell Cmdlets Or Not Executing Against a VM

I need to execute PowerShell on VMs from an Azure Automation Runbook, akin to a WinRm execution/PowerShell Remoting.
I have created an Azure Runbook through the Azure Automation GUI, and am trying to run a script that works perfectly against physical and virtual machines to get key system information and ports. I am able to authenticate in Azure and it appears that I can execute some aspects of the script (unless it's only running against the Azure Automation Worker) via the Azure Runbook, such as getting the installed PowerShell Version of the targeted VMs using: $PSVersionTable.PSVersion so I am not having issues with security/access from what I can tell.
However, several other components fail as follows, and I don't know if I need to import a Module to Azure Automation and if so, which ones. Or if this is failing because it is running against the Worker and not the VMs.
Here are some of the code snippets I am running:
$computerSystem = Get-CimInstance Win32_ComputerSystem
"CPU: " + $computerCPU.Name
Get-WmiObject -Class Win32_LogicalDisk |
Where-Object {$_.DriveType -ne 5} |
Sort-Object -Property Name |
Select-Object Name, VolumeName, FileSystem, Description, `
#{"Label"="DiskSize(GB)";"Expression"={"{0:N}" -f ($_.Size/1GB) -as [float]}}, `
#{"Label"="FreeSpace(GB)";"Expression"={"{0:N}" -f ($_.FreeSpace/1GB) -as [float]}}, `
#{"Label"="%Free";"Expression"={"{0:N}" -f ($_.FreeSpace/$_.Size*100) -as [float]}} |
Format-Table -AutoSize
Get-NetAdapter -Name "*" | Format-Table
Get-NetOffloadGlobalSetting | Format-List
Test-NetConnection -Port 80
Here are the error messages, which I strongly suspect are EITHER due to missing PowerShell Modules that I need to upload, but am unsure where to find these OR is this a situation where I am not targeting the VM correctly and instead running this against the AZ Host? (if so, any good examples of how to target a single VM):
Get-CimInstance : The specified service does not exist as an installed
service.
Get-WmiObject : The specified service does not exist as an installed
service.
Get-NetAdapter : The term 'Get-NetAdapter' is not recognized as the
name of a cmdlet, function, script file, or operable program.
Get-NetOffloadGlobalSetting : The term 'Get-NetOffloadGlobalSetting'
is not recognized as the name of a cmdlet, function, script file, or
operable program.
Test-NetConnection : The term 'Test-NetConnection' is not recognized
as the name of a cmdlet, function, script file, or operable program.
If it is an issue with targeting the VM properly, I need some guidance. I suspect that I am targeting the Worker running the Runbooks and not the actual VMs. I am using the RunAs account/the new Azure Automation security methods (not classic) so I don't believe certificates come into play. Here is how I am trying to target the VM (which I suspect is incorrect/should be changed):
$Resources = Get-AzureRmResource -ResourceType "Microsoft.Compute/virtualMachines" -ResourceGroupName "MyTestGroup" -ResourceName "MyTestVM"
ForEach ($Resource in $Resources)
{
# PowerShell Code from Above here
}
UPDATE 1:
Now that we have determined that I am not targeting the VM properly, I tried Joe's recommendation, but when I try to run the following I get an error on the WinRm. I found the Connect-AzureVM.ps1, but am unsure if this is old or aligns to the newer RunAs Connection I am using. Here is my current script that attempts to connect to the VM and Invoke PowerShell.
param(
[parameter(Mandatory=$true)][String] 'https://myvmname.eastus.cloudapp.azure.com:5986,
[parameter(Mandatory=$true)][String] 'MyVMName'
)
$connectionName = "AzureRunAsConnection"
try
{
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
throw $_.Exception
}
}
# Get credentials to Azure VM
$Credential = Get-AutomationPSCredential -Name $VMCredentialName
Invoke-Command -ConnectionUri $Uri -Credential $Credential -ScriptBlock {
# My PowerShell Here
}
This is the error the script produces. I suspect its because I need to import/create a WinRM certificate on the VM I am targeting, but unsure if the Connect-AzureVM.ps1 is the right script to use or if there is another/more updated method to use for WinRM access:
[myvmname.eastus.cloudapp.azure.com] Connecting to remote server
myvmname.eastus.cloudapp.azure.com failed with the following error
message : WinRM cannot complete the operation. Verify that the
specified computer name is valid, that the computer is accessible
over the network, and that a firewall exception for the WinRM service
is enabled and allows access from this computer. By default, the
WinRM firewall exception for public profiles limits access to remote
computers within the same local subnet. For more information, see the
about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (myvmname.eastus.cloudapp.azure.com:String) [],
PSRemotingTransportException
+ FullyQualifiedErrorId : WinRMOperationTimeout,PSSessionStateBroken
Run this inside the VM at an elevated prompt
https://gist.github.com/jeffpatton1971/2321f0db8025e48ad8ec13c243153045
From inside your Runbook do whatever you normally to wire it up but create some session options to pass along your invoke-command.
$SessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck
Invoke-Command -ComputerName $VMname -Credential $Credential -UseSSL -SessionOption $SessionOption -ScriptBlock { }
You need to either add the VMs you want to run these scripts on as Azure Automation hybrid workers, so that you can target the script to run on them, or you need to, from your runbook running on Azure Automation's own workers, remote into each VM and from within the remoting block run the commands.
For the former, see: https://learn.microsoft.com/en-us/azure/automation/automation-hybrid-runbook-worker
For the ladder:
Invoke-Command -ConnectionUri $Uri -Credential $Credential -ScriptBlock {
$computerSystem = Get-CimInstance Win32_ComputerSystem
"CPU: " + $computerCPU.Name
Get-WmiObject -Class Win32_LogicalDisk |
Where-Object {$_.DriveType -ne 5} |
Sort-Object -Property Name |
Select-Object Name, VolumeName, FileSystem, Description, `
#{"Label"="DiskSize(GB)";"Expression"={"{0:N}" -f ($_.Size/1GB) -as [float]}}, `
#{"Label"="FreeSpace(GB)";"Expression"={"{0:N}" -f ($_.FreeSpace/1GB) -as [float]}}, `
#{"Label"="%Free";"Expression"={"{0:N}" -f ($_.FreeSpace/$_.Size*100) -as [float]}} |
Format-Table -AutoSize
Get-NetAdapter -Name "*" | Format-Table
Get-NetOffloadGlobalSetting | Format-List
Test-NetConnection -Port 80
}

How to avoid the crendetial dialog box while conecting to different system using powershell?

I am making connection to the diferent system using powershell. I want this process to be automated in a way that once user hits the powershell script he gets connected to different system without entering username and password details into the dialog box. So, currently my.PS1 script as follows:
Enable-PSRemoting -Force
Set-Item wsman:\localhost\client\trustedhosts CP0001256
Restart-Service WinRM
Test-WsMan CP0001256
$credential = Import-CliXml -Path "D:\$Env:USERNAME_pass.xml"
Invoke-Command -ComputerName CP0001256-ScriptBlock { Get-ChildItem D:\ }-credential $credential
Before running my.PS1 i have executed follwing script:
$credential = Get-Credential
$credential | Export-CliXml -Path "D:\$Env:USERNAME_pass.xml"
So, when i execute my.PS1 i got error as:
Invoke-Command : Cannot process argument transformation on parameter 'Credential'. username
At my.PS1:7 char 86
+ Invoke-Command -ComputerName CP0001256-ScriptBlock { Get-ChildItem D:\ } -credential <<<< $credential
+ CategoryInfo : InvalidData: (:) [Invoke-Command], ParameterBindin..mationException
+ FullyQualifiedErrorID : ParameterArgumentTransformationError,Microsoft.Powershell.Commands.InvokeCommand
So, tell me what i am doing wrong and how can i avoid getting the credential dialog box pop up appearing.
This is a question of how to store credentials in a script. Keep in mind that this always carries some risk. You can of course store them in plain text. Then anyone with access to the script has those credentials.
Another thing you can do is take advantage of the [PSCredential] object, and store the password encrypted. Consider running this code (outside of that script):
$credential = Get-Credential # dialog pops up here, enter server creds
$credential | Export-CliXml -Path "C:\Script\$Env:USERNAME_Credential.xml"
Now in your script, you can do this:
$credential = Import-CliXml -Path "C:\Script\$Env:USERNAME_Credential.xml"
Invoke-Command -ComputerName CP0001256-ScriptBlock { Get-ChildItem D:\ }-credential $credential
The password is encrypted within that XML file, and it's encrypted with a key that is specific to the user who ran the first set of commands, so only that user will be able to effectively run the script if you do this.
This is also why I use the USERNAME environment variable as part of the file name. You can have multiple employees run the first code snippet to generate a separate encrypted file for each of them. Then your script will work successfully when any of them run it.
It also works if you have an account used for a scheduled task for example; run the snippet as that user once, then the scheduled task will work.

Powershell Invoke-Command Operations Error

I'm stumped by this issue.
I've written a powershell script which I'm trying to use to import a GPO across multiple domains and then link it with new-gplink. I've made sure all servers have GP Powershell module installed and it's been working pretty well so far, however the issue I'm running into is that on some servers my command works fine on others I get the error, on the last step I'm getting an operations error one of my invoke-commands. Other commands work on the same server with invoke-command such as get-service, or even the import-GPO command that I use.
The error in question:
An operations error occurred. (Exception from HRESULT: 0x80072020)
+ CategoryInfo : NotSpecified: (:) [New-GPLink], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Microsoft.GroupPolicy.Commands.NewGPLinkCommand
+ PSComputerName : 10.0.0.10
The command:
Invoke-Command -ComputerName $serverip -scriptblock {New-GPLink -Name "GPO" -Target $args[0]} -ArgumentList $oupath -credential $cred
I've tried every version of this command I can imagine. without [0], without argument list, just using the server ip and replacing the target with the OU path and I still get the same error, such as below.
Invoke-Command -ComputerName $serverip -scriptblock {New-GPLink -Name "GPOName" -Target ou=users,ou=site,ou=domain,dc=server,dc=com} -ArgumentList $oupath -credential $cred
The way I have it working is a .csv with the server info, it gets imported into a foreach loop and then fed into the script. I have it grab credentials and feed through. I know everything else is working because my invoke-command to import the GPO worked, all servers I ran to successfully imported the GPO. I also know my OU paths are correct because I use them locally with another script to place computers where I want them. a sample line in the csv would be something like
servername, 10.0.0.10, domain.com, OU=user,OU=site,DC=domain,DC=com
I've also ran the command locally and get a similar error:
PS> New-GPLink -Name "GPO" -Target "ou=users,ou=Site,dc=domain,dc=com"
New-GPLink : A referral was returned from the server.
At line:1 char:1
+ New-GPLink -Name "GPO" -Target "ou=users,ou=site,dc=domain,d ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-GPLink], DirectoryServicesCOMException
+ FullyQualifiedErrorId : System.DirectoryServices.DirectoryServicesCOMException,Microsoft.GroupPolicy.Commands.NewGPLinkCommand
Please let me know if there are additional question or if you need additional info. I'm completely stumped by this issue and I appreciate any help you can provide. Thanks in advance.
Edit: All of my servers are at least 2008 R2 and are using powershell version 3,0,1,1
PS> $psversiontable.psversion
Major Minor Build Revision
----- ----- ----- --------
3 0 -1 -1
You need to specify a the domain in which your trying to apply the GPO, as well as a Domain Controller from the domain in question with the -Domain and -Server parameters respectively:
$OU = "ou=users,ou=Site,dc=domain,dc=com"
New-GPLink -Name "GPO" -Target $OU -Server "domain.com" -Domain "domain.com"
Instead of just using the domain name though, the proper way to do this, is to actually locate a Domain Controller, like so:
$DC = Get-ADDomainController -Discover -DomainName "domain.com" |Select -ExpandProperty HostName
New-GPLink -Name "GPO" -Target $OU -Server $DC -Domain "domain.tld"
Or in an environment where Get-ADDomainController is not available, you can emulate the DCLocator (aka. the underlying high-availability design of AD DS) behavior with .NET:
$DomainFqdn = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dctx = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList "Domain",$DomainFqdn
$DomainController = $[System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($dctx)
New-GPLink -Name "GPO" -Target $OU -Server $DomainController.Name -Domain $DomainFqdn