3

I have a really strange situation where the same command works when run manually from a Windows Server 2012 R2, but not from the Jenkins slave process running on the same server.

First, the output from the manual run, an admin PowerShell window:

PS C:\Users\Administrator> whoami
win-cm8utd1qfnc\administrator
PS C:\Users\Administrator> Invoke-Command -computername web.sandbox.MUNGED.com -scriptblock {iisreset /restart}
Attempting stop...
Internet services successfully stopped
Attempting start...
Internet services successfully restarted

Great. Now, the relevant snippet of Jenkins pipeline code:

pipeline {
    stages {
        stage('Deploy web') {
            agent { label 'windows-server-2012' }
            environment {
                SERVER = 'web.sandbox.MUNGED.com'
            }
            steps {
                powershell """
                    whoami
                    Invoke-Command -computername ${SERVER} -scriptblock {iisreset /restart}
                """

            }
        }
    }
}

And the output when running from Jenkins:

07:37:29 win-cm8utd1qfnc\administrator
07:37:29 [web.sandbox.MUNGED.com] Connecting to remote server web.sandbox.MUNGED.com failed with the following error message : Access is denied. For more information, see the 
07:37:29 about_Remote_Troubleshooting Help topic.
07:37:29     + CategoryInfo          : OpenError: (web.sandbox.MUNGED.com:String) [], PSRemotingTransportException
07:37:29     + FullyQualifiedErrorId : AccessDenied,PSSessionStateBroken

The Windows servers (Jenkins slave and web server) are not part of a domain, but have the same Administrator password, which seems to make the authentication work well.

For what it's worth, here's the winrm configuration for the Jenkins slave:

PS C:\Users\Administrator> winrm get winrm/config
Config
    MaxEnvelopeSizekb = 500
    MaxTimeoutms = 1800000
    MaxBatchItems = 32000
    MaxProviderRequests = 4294967295
    Client
        NetworkDelayms = 5000
        URLPrefix = wsman
        AllowUnencrypted = false
        Auth
            Basic = true
            Digest = true
            Kerberos = true
            Negotiate = true
            Certificate = true
            CredSSP = false
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        TrustedHosts = *
    Service
        RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
        MaxConcurrentOperations = 4294967295
        MaxConcurrentOperationsPerUser = 1500
        EnumerationTimeoutms = 240000
        MaxConnections = 300
        MaxPacketRetrievalTimeSeconds = 120
        AllowUnencrypted = true
        Auth
            Basic = true
            Kerberos = true
            Negotiate = true
            Certificate = false
            CredSSP = false
            CbtHardeningLevel = Relaxed
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        IPv4Filter = *
        IPv6Filter = *
        EnableCompatibilityHttpListener = false
        EnableCompatibilityHttpsListener = false
        CertificateThumbprint
        AllowRemoteAccess = true
    Winrs
        AllowRemoteShellAccess = true
        IdleTimeout = 7200000
        MaxConcurrentUsers = 10
        MaxShellRunTime = 2147483647
        MaxProcessesPerShell = 4096
        MaxMemoryPerShellMB = 8192
        MaxShellsPerUser = 30

And from the webserver:

PS C:\Users\Administrator> winrm get winrm/config
Config
    MaxEnvelopeSizekb = 500
    MaxTimeoutms = 1800000
    MaxBatchItems = 32000
    MaxProviderRequests = 4294967295
    Client
        NetworkDelayms = 5000
        URLPrefix = wsman
        AllowUnencrypted = false
        Auth
            Basic = true
            Digest = true
            Kerberos = true
            Negotiate = true
            Certificate = true
            CredSSP = false
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        TrustedHosts = *
    Service
        RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
        MaxConcurrentOperations = 4294967295
        MaxConcurrentOperationsPerUser = 1500
        EnumerationTimeoutms = 240000
        MaxConnections = 300
        MaxPacketRetrievalTimeSeconds = 120
        AllowUnencrypted = true
        Auth
            Basic = true
            Kerberos = true
            Negotiate = true
            Certificate = false
            CredSSP = false
            CbtHardeningLevel = Relaxed
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        IPv4Filter = *
        IPv6Filter = *
        EnableCompatibilityHttpListener = false
        EnableCompatibilityHttpsListener = false
        CertificateThumbprint
        AllowRemoteAccess = true
    Winrs
        AllowRemoteShellAccess = true
        IdleTimeout = 7200000
        MaxConcurrentUsers = 10
        MaxShellRunTime = 2147483647
        MaxProcessesPerShell = 25
        MaxMemoryPerShellMB = 1024
        MaxShellsPerUser = 30

EDIT: I got it to work after a fashion. Firstly, on the Jenkins slave, I had to run:

winrm set winrm/config/client '@{AllowUnencrypted="true"}'

Then I changed the pipeline to:

powershell """
    \$creds = Import-CliXml \$home\\creds.xml
     Invoke-Command -computername ${SERVER} -scriptblock {iisreset /restart} -Authentication Basic -Credential \$creds
"""

where creds.xml was a file previously generated with Get-Credentials | Export-CliXml creds.xml.

That still doesn't explain why the behaviour is different between manual PowerShell and Jenkins slave. It's a bit of an annoying workaround, but at least I can proceed.

antgel
  • 1,241
  • 1
  • 14
  • 29
  • Does the Jenkins slave run with lower permissions? It's possible to run in the same user account but with only a subset of privileges for security reasons (for instance how the Chrome browser runs its browser tabs with more restrictions than the user account it runs under). – NextInLine Apr 09 '18 at 19:32
  • I would have thought that Administrator was Administrator. How can I check these privileges you speak of? – antgel Apr 09 '18 at 19:51
  • [Here's a good write-up on the matter](https://blogs.technet.microsoft.com/heyscriptingguy/2011/05/11/check-for-admin-credentials-in-a-powershell-script/). – Maximilian Burszley Apr 09 '18 at 20:09
  • I checked it, and that doesn't seem to be the issue. I ran `([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")` and it came back as True, both from the Jenkins slave and when running manually in PowerShell. What should I check next? – antgel Apr 10 '18 at 07:21
  • I added a workaround to the bottom of the OP, but it still doesn't explain why the behaviour is different between manual PowerShell and Jenkins slave. Happy to hear from anyone with a clue. – antgel Apr 10 '18 at 09:13

2 Answers2

1

You are probably hitting the remote execution script limitation from Jenkins (security is the cause here). You need to configure the Jenkins server to be able to run the script "normally" but you will always have to add the credentials.

The script you are running from powershell command line uses the default credentials for your win-cm8utd1qfnc\administrator so the following will work as you wrote:

PS C:\Users\Administrator> whoami
win-cm8utd1qfnc\administrator
PS C:\Users\Administrator> Invoke-Command -computername web.sandbox.MUNGED.com -scriptblock {iisreset /restart}
Attempting stop...
Internet services successfully stopped
Attempting start...
Internet services successfully restarted

However, when running Powershell from Jenkins, a plugin by its nature, you are hitting the security-by-design limitation. You don't want to run "wild" scripts with your Administration account.

The most reasonable guide I have found on this topic was here (the following is excerpt from the page:

Executing Powershell scripts/commands remotely

he above job creates a text file on the Jenkins server itself. To setup remote Powershell scripts we first need to configure Jenkins server for remote Powershell script execution. To enable remote Windows machine in to the WS-Man trusted list on Jenkins servers. Execute the below command on Powershell window on Jenkins server. The command will add all the remote machine to the trusted list.

Set-Item WSMan:\localhost\Client\TrustedHosts *

Along with the commands, we would also need to enable remote script execution also. To enable execution of Powershell scripts remotely execute the following command in Powershell window on Jenkins server.

Set-ExecutionPolicy RemoteSigned –Force

We will have to install a new plugin named EnvInject Plugin for transferring variables e.g. passwords.

Login to Jenkins and navigate to Manage Jenkins > Manage Plugins
Click on the Available tab and Enter EnvInject in the filter box
Select the plugin showing by name PowerShell Plugin
Select Download now and install after restart

Creating a job to restart Windows time service:

On Jenkins interface, click New Item
Enter Remote Powershell scripts for the job name. Select Freestyle project
Tick This build is parameterized. Create following parameters
    Type: String Parameter
    Name: ServerIp/Hostname
    Description: Remote machine’s IP address.
    Type: String Parameter
    Name: UserName
    Type: Password Parameter
    Name: Password
Now, Click Add Parameter list and select the Choice Parameter. Enter the options on new lines inside the Choices text box. Also,

provide description for the options mentioned:

The following script is based on the above link, but I did not like the plain text used so I have decided to rewrite it to use Powershell's SecureString

First to store your admin password:

read-host -AsSecureString | ConvertFrom-SecureString | Out-File C:\<your_path>\securestring.txt

Then have the script:

# Configure build failure on errors on the remote machine similar to set -x on bash script 
$ErrorActionPreference = 'Stop'  

# Create a PSCredential Object using the "Username" and "Password" variables created on job 
$password = Get-Content 'C:\<your_path>\securestring.txt' | ConvertTo-SecureString
$creddentials = New-Object System.Management.Automation.PSCredential -ArgumentList $env:UserName, $password

# It depends on the type of job you are executing on the remote machine as to if you want to use "-ErrorAction Stop" on your Invoke-Command. 
Invoke-Command -ComputerName $env:Computer -Credential $credentials -ScriptBlock { Restart-Service -Name W32Time }
tukan
  • 17,050
  • 1
  • 20
  • 48
  • Thanks for the answer, but I'm not sure there's anything new there. You can see in the OP that I already have `TrustedHosts = *`. Do you have any more detailed information on this "remote execution script limitation from Jenkins"? I couldn't find much. It looks like Jenkins [sets a very liberal execution policy](https://github.com/jenkinsci/powershell-plugin/blob/d3e8aec4074bdfa5f5032c71a3e6ba4a3c6c8340/src/main/java/hudson/plugins/powershell/PowerShell.java#L29) when running Powershell commands. – antgel Apr 10 '18 at 10:42
  • @topper Part1 comment: well for Powershell you have to do some extra steps - that is issue of the plugin not Jenkins. If you check the original plugin's change log (https://wiki.jenkins.io/display/JENKINS/PowerShell+Plugin) you will see a note *PowerShell now runs with ExcecutionPolicy set to "Bypass" to avoid execution policy issues* which indicates that the pluging has to be "forced" to run correctly. – tukan Apr 10 '18 at 10:56
  • @topper Part2 comment: To run *Run an Administrative PowerShell Prompt as normal* you have to do `Set-ExecutionPolicy RemoteSigned –Force`. Did you do so? You can also store the credetials in Jenkins itself which would fix you quite well more at - https://hodgkins.io/automating-with-jenkins-and-powershell-on-windows-part-2#storing-credentials-in-jenkins – tukan Apr 10 '18 at 10:56
  • @topper imagine it in the same way as you being in the shell (powershell.exe) and using Powershell ISE. Jenkins powershell is a plugin, you are not using shell directly. The plugin imposes its own security measures. – tukan Apr 11 '18 at 08:00
0

See this question: Remote Access with Powershell and Jenkins

Need to change service user from Local System to administrator.

Der_Meister
  • 4,771
  • 2
  • 46
  • 53