An analysis of the task http://pricing-deploy.sportingsolutions.com/deploy/app#/Spaces-1/projects/ipa-football-dal-simulation-service/deployments/process The task has 8 steps.
- The first step uses PowerShell to stop and remove the service.
$name = $OctopusParameters['name']
# Kill any service management windows that are open as these can impact script
# http://support.microsoft.com/kb/823942
Get-Process -n "mmc" -ErrorAction SilentlyContinue | Stop-Process -ErrorAction SilentlyContinue
# Stop and delete existing service
$service = Get-Service $name -ErrorAction SilentlyContinue
if ($service -ne $null)
{
Write-Host "Service exists, gracefully stopping"
if($service.Status -eq "Running")
{
$servicePID = (Get-Wmiobject win32_Service | Where { $_.Name -eq $service.Name }).ProcessID
$stopTries = 0
do
{
$service | Stop-Service -Force -ErrorAction SilentlyContinue
#allow service time to perform status change
Start-Sleep -seconds 3
#update status
$service = Get-Service $name -ErrorAction SilentlyContinue
$stopTries += 1
} #keep trying until Servic is Stopped or Tries exceed 3
until($service.Status -eq "Stopped" -or $stopTries -ge 3)
$ProcessActive = Get-Process -Id $servicePID -ErrorAction SilentlyContinue
if($ProcessActive -ne $null)
{
Write-Host "Service process exists, killing process"
Stop-Process $servicePID -Force -erroraction 'silentlycontinue'
}
}
Write-Host "Deleting service"
$toDelete = Get-WmiObject -Class Win32_Service -Filter "name='$name'"
$toDelete.delete()
}
else
{
Write-Host "Service does not exist"
}
Write-Host "Complete"
This task is way overcomplicated. With Ansible all would be needed is:
# https://docs.ansible.com/ansible/latest/collections/ansible/windows/win_service_module.html
- name: Remove a service
ansible.windows.win_service:
name: service name
state: absent
- This step is creating a local user account (to be used by this service). It again uses PowerShell.
$username = $OctopusParameters['Username']
$password = $OctopusParameters['Password']
$memberOf = $OctopusParameters['MemberOf']
$userRights = $OctopusParameters['UserRights']
# Add/Update User
$user = Get-WmiObject Win32_UserAccount -filter "LocalAccount=True AND Name='$username'"
if($user -eq $null)
{
Write-Host "Adding user $username"
net user "$username" "$password" /add /expires:never /passwordchg:no /yes
}
else
{
Write-Host "User $username already exists, updating password"
net user "$username" "$password" /expires:never /passwordchg:no
}
# Ensure password never expires
Write-Host "Ensuring password never expires"
net accounts /maxpwage:unlimited
WMIC USERACCOUNT WHERE "Name='$username'" SET PasswordExpires=FALSE
# Add/Update group membership
if($memberOf)
{
$groups = $memberOf.Split(",")
foreach($group in $groups)
{
$ntGroup = [ADSI]("WinNT://./$group")
$members = $ntGroup.psbase.Invoke("Members") | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
if($members -contains "$username")
{
Write-Host "User already a member of the $group group"
}
else
{
Write-Host "Adding to the $group group"
net localgroup "$group" "$username" /add
}
}
}
# Add/Update user rights assignment
if($userRights)
{
$userRightsArr = $userRights.Split(",")
foreach($userRight in $userRightsArr)
{
Write-Host "Granting $userRight right"
& "ntrights" +r $userRight -u "$username"
}
}
This is again a massive overcomplication. Mixing cmd
and powershell
commands (as in the above script) is also not a great idea, not to mention the reliance on ntrights.exe', a file from an old and unsuported (like 20 years-old)
.exe` file. (Goes without saying using this old file is something even Microsoft does not endorse, as the kit it was part of no longer available from their site - it is a security risk that should be eliminated!)
- If one prefers PowerShell, Microsoft has a complete module to do any CRUD operation on local users and groups -->
New-LocalUser
andAdd-LocalGroupMember
can do most of the work. For assigning theSeServiceLogonRight
probably a custom module is needed, for example this one (haven't tested it, might need some refinements) - Alternatively Ansible is of course capable all of the above in a few simple steps:
## cat tasks/main.yaml
---
- name: "Ensure {{ local_group }} is present"
ansible.windows.win_group:
name: "{{ local_group }}"
description: "{{ local_group_desc }}"
state: present
- name: "Ensure {{ user_account }} is present"
ansible.windows.win_user:
name: "{{ local_user }}"
# password: B0bP4ssw0rd
state: present
groups:
- "{{ local_group }}"
- name: "Add {{ local_right }} to {{ user_account }}"
win_user_right:
name: "{{ local_right }}"
users:
- "{{ local_user }}"
action: add
## cat defaults/main.yaml
---
local_user: "techops"
local_group: "ServiceAccounts"
local_group_desc: "Group for Service Accounts"
local_right: SeServiceLogonRight
This task does again a simple thing overcomplicated:
- our script:
## I dont list the parameters here, they come from Octopus variables
$ruleName = $OctopusParameters['RuleName']
$localPort = $OctopusParameters['LocalPort']
$remotePort = $OctopusParameters['RemotePort']
$protocol = $OctopusParameters['Protocol']
$direction = $OctopusParameters['Direction']
# Remove any existing rule
Write-Host "Removing existing rule"
netsh advfirewall firewall delete rule name="$ruleName" dir=$direction
# Add new rule
Write-Host "Adding new rule"
netsh advfirewall firewall add rule name="$ruleName" dir=$direction action=allow protocol=$protocol localport=$localPort remoteport=$remotePort
what it should be really:
# https://docs.ansible.com/ansible/latest/collections/community/windows/win_firewall_rule_module.html
- name: <RuleName>
community.windows.win_firewall_rule:
name: <RuleName>
localport: <LocalPort>
action: allow
direction: in
protocol: <Protocol>
# profiles: domain # optional; defaults to 'domain,private,public
state: present
enabled: yes
```
The current task is:
- removing the firewall rule (needlessly)
- than recreates the same rule (needlessly) It should really do a logical check of "is it exist - if not, create" --> indempotency is given with Ansible
- it is also managed to - in just for lines of actual code! - mix
cmd
andpowerhsell
; totally unneccesarily, as PowerShell has a command to manage firewall rules, if that is something we want...
- Messaging Firewall Same as the previous step, just with a different port
- Deploy Service This task uses Octopus's built-in mechanisms to deploy an application from a NuGet feed. This is probably the only step where there are questions weather this could be done via Ansible for example. As I am rarely dealing with application development, can't say for certain, but there is a good chance this module or this one using Chocolatey would be more appropriate.
- File System - Permissions The last (active; there are some disabled steps) action step is setting some file permissions:
$item = $OctopusParameters['Item']
$readPermissionsTo = $OctopusParameters['ReadPermissionsTo']
$writePermissionsTo = $OctopusParameters['WritePermissionsTo']
# Check item exists
if(!(Test-Path $item))
{
throw "$item does not exist"
}
# Assign read permissions
if($readPermissionsTo)
{
$users = $readPermissionsTo.Split(",")
foreach($user in $users)
{
Write-Host "Adding read permissions for $user"
$acl = Get-Acl $item
$acl.SetAccessRuleProtection($False, $False)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$user, "Read", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
Set-Acl $item $acl
}
}
# Assign write permissions
if($writePermissionsTo)
{
$users = $writePermissionsTo.Split(",")
foreach($user in $users)
{
Write-Host "Adding write permissions for $user"
$acl = Get-Acl $item
$acl.SetAccessRuleProtection($False, $False)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$user, "Write", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
Set-Acl $item $acl
}
}
Write-Host "Complete"
This can be done much simler way with this module
#random example
- name: Read ACL
ansible.windows.win_acl:
path: < Item >
user: < ReadPermissionsTo >
rights: Read
type: allow
state: present
- name: Write ACL
ansible.windows.win_acl:
path: < Item >
user: < WritePermissionsTo >
rights: Read
type: allow
state: present