Ansible Playbook | Need to execute powershell script - ansible

I am wondering if someone can help. I am trying to find a way to run a powershell script within an ansible playbook.
I found a module that seems to allow it but I am unable to make it work.
https://docs.ansible.com/ansible/latest/collections/ansible/windows/win_powershell_module.html
The PS script that I need to run within the ansible playbook is the following:
# Step 1
$net = new-object -ComObject WScript.Network
$net.MapNetworkDrive("X:", "\\fileserver", $true, $USER, $PASS)
Copy-Item "x:\*" -Destination c:\scripts -recurse -force
# Replace user and run installer
$GenericPath = "C:\pts\Install.bat"
$name = (Get-Content C:\pts\file.txt)
$pos = $name.IndexOf("#")
$User = $name.Substring(0, $pos)
#$Domain = $name.Substring($pos+1)
if ($name -match 'dom1.al.com')
{
$Domain = "al"
}
if ($name -match 'dom2.al.com')
{
$Domain = "al2"
}
if ($name -match 'dom3.al.com')
{
$Domain = "al3"
}
if ($name -match 'dom4.al.com')
{
$Domain = "al4"
}
$ReplaceUser = $Domain + '\' + $User
(Get-Content $GenericPath) | Foreach-Object {$_ -replace "ReplaceUser","$ReplaceUser"} | Set-Content $GenericPath
F:\pts\reconfigure.bat
Can someone please let me know how should the ansible playbook needs to look like in order to get this to work. The target VMs where the PS will be executed via ansible are Windows OS. I understand I can run PS directly on those servers but the execution for this workflow needs to happen from within an ansible playbook.
Thank you

Well, it worked with RHEL, so the same might work in Windoze. Try this:
---
- name: Run PowerShell script
hosts: all
become: no
gather_facts: no
tasks:
- win_shell: "{{ item }}"
with_file:
- my_kewel_powershell_script.ps
...

Related

Folder audit Powershell script, nested loop

I am trying to get a script working to audit folder permissions on a Windows server, among other data, and export this data to a CSV file for analysis after a ransomware attack.
I ripped the script from a forum, but it did not run correctly as is. Below is a slightly modified version during my troubleshooting.
I am well versed in batch scripting, and have a decent understanding of loops and pipelining, but this Powershell script has me scratching my head.
It seems like the array is not making it to the nested loop.
I am testing in Windows 10 Pro 21H1, using Powershell version 5.1.19041.1320, build 10.0.19041.1320
##The script:
$ErrorActionPreference = "Continue"
$strComputer = $env:ComputerName
$colDrives = Get-PSDrive -PSProvider Filesystem
ForEach ($DriveLetter in $colDrives) {
$StartPath = "$DriveLetter`:\"
Get-ChildItem -LiteralPath $StartPath -Recurse | ?{ $_.PSIsContainer } |
ForEach ($FullPath = Get-Item -LiteralPath{Get-Item -LiteralPath $_.PSPath}{Get-Item -
LiteralPath $FullPath}.Directoryinfo.GetAccessControl())}
Select #{N='Server Name';E={$strComputer}}
#{N='Full Path';E={$FullPath}}
#{N='Type';E={If($FullPath.PSIsContainer -eq $True) {'D'} Else {'F'}}}
#{N='Owner';E={$_.Owner}}
#{N='Trustee';E={$_.IdentityReference}}
#{N='Inherited';E={$_.IsInherited}}
#{N='Inheritance Flags';E={$_.InheritanceFlags}}
#{N='Ace Flags';E={$_.PropagationFlags}}
#{N='Ace Type';E={$_.AccessControlType}}
#{N='Access Masks';E={$_.FileSystemRights}}
Export-CSV -NoTypeInformation -Delimiter "|" -Path "$strComputer`_$DriveLetter.csv"
##The error I am getting:
You cannot call a method on a null-valued expression.
At C:\Users\user\Documents\fileaudit2.ps1:8 char:13
ForEach ($FullPath = Get-Item -LiteralPath{Get-Item -LiteralPath $ ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
##when I modify the nested loop as follows:
ForEach ($FullPath = Get-Item -LiteralPath{Get-Item -LiteralPath $_.PSPath}{Get-Item -LiteralPath $FullPath}).Directoryinfo.GetAccessControl()}
##I get the error:
Get-Item : Cannot evaluate parameter 'LiteralPath' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without
input.
At C:\Users\user\Documents\fileaudit2.ps1:8 char:46
... Path = Get-Item -LiteralPath{Get-Item -LiteralPath $_.PSPath}{Get-Ite ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : MetadataError: (:) [Get-Item], ParameterBindingException
FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.GetItemCommand
##I'm just wholly struggling to understand what is not working in this loop.
You are mixing a lot of unneeded Get-Item calls in there.
I also would not use Get-PSDrive for this because I assume you don't want to get results for CD drives, USB devices etc in the report.
Try:
# this returns drives WITH a trailing backslash like C:\
$colDrives = ([System.IO.DriveInfo]::GetDrives() | Where-Object { $_.DriveType -eq 'Fixed' }).Name
# or use:
# this returns drives WITHOUT trailing backslash like C:
# $colDrives = (Get-CimInstance -ClassName win32_logicaldisk | Where-Object { $_.DriveType -eq 3 }).DeviceID
$result = foreach ($drive in $colDrives) {
Get-ChildItem -LiteralPath $drive -Directory -Recurse -ErrorAction SilentlyContinue |
ForEach-Object {
$path = $_.FullName
$acl = Get-Acl -Path $path
foreach ($access in $acl.Access) {
[PsCustomObject]#{
Server = $env:COMPUTERNAME
Drive = $drive[0] # just the first character of the drive
Directory = $path
Owner = $acl.Owner
Trustee = $access.IdentityReference
Inherited = $access.IsInherited
InheritanceFlags = $access.InheritanceFlags -join ', '
'Ace Flags' = $access.PropagationFlags -join ', '
'Ace Type' = $access.AccessControlType
'Access Masks' = $access.FileSystemRights -join ', '
}
}
}
}
# now you can save your result as CSV file for instance you can double-click to open in Excel:
$result | Export-Csv -Path 'X:\WhereEver\audit.csv' -NoTypeInformation -UseCulture
To do this on several remote machines, wrap it inside Invoke-Command
# set the credentials for admin access on the servers
$cred = Get-Credential 'Please enter your admin credentials'
# create an array of the servers you need to probe
$servers = 'Server01', 'Server02'
$result = Invoke-Command -ComputerName $servers -Credential $cred -ScriptBlock {
$colDrives = ([System.IO.DriveInfo]::GetDrives() | Where-Object { $_.DriveType -eq 'Fixed' }).Name
foreach ($drive in $colDrives) {
# code inside this loop unchanged as above
}
}
# remove the extra properties PowerShell added
$result = $result | Select-Object * -ExcludeProperty PS*, RunspaceId
# output to csv file
$result | Export-Csv -Path 'X:\WhereEver\audit.csv' -NoTypeInformation -UseCulture

Have I written this PowerShell script well enough to balance simplicity and performance?

I have a script that checks remote servers for tomcat and the associated java versions. It takes about 60 seconds to run against a list of about 16 servers. I'm just curious if the script is as efficient as realistically possible. I'm far from a PowerShell pro but I'm satisfied with the outcome. Just checking for where there is room for improvement.
$Servers = 'server1','server2','etc'
$Output = #()
foreach ($Server in $Servers)
{
$SName = gwmi -Class Win32_Service -ComputerName $Server -Filter {Name LIKE 'Tomcat%'}
IF ($SName -ne $null) {
$Output += [PSCustomObject]#{
Server_name = $SName.PSComputerName
Service_name = $SName.Name
Service_status = $SName.State
Tomcat_version = "$(Get-Content -Path ("\\"+$SName.PSComputerName+"\"+"$($SName.PathName.ToString())".Substring(0,$SName.Pathname.LastIndexOf("\")-3)+"\webapps\ROOT\RELEASE-NOTES.txt" -replace ":", "$") | Select-String -Pattern 'Apache Tomcat Version ')".TrimStart()
Java_Version = (Invoke-Command -ComputerName $Server -ScriptBlock {(GCI -Path "$((Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\Tomcat9\Parameters\Java').Jvm)").VersionInfo.ProductName})
}
}
Else {}
}
$Output | Select Server_name, Service_name,Service_status, Tomcat_Version, Java_Version | Format-Table -AutoSize
Can I simplify things anymore?
Is the time to completion decent for what is being performed?
Invoke-Command allows you to connect with multiple computers at the same time which should be more efficient.
+= is pretty bad, please read: Why should I avoid using the increase assignment operator (+=) to create a collection
You're querying WMI first and then if service is there you are using Invoke-Command, mind as well, connect once to the remote host and check everything.
I personally would do something like this
$Servers = 'server1','server2','etc'
$scriptBlock = {
# Since you're querying each server, and then if the service is there you Invoke-Command,
# mind as well Invoke-Command at first and if the service is there enter the If condition,
# else close the connection.
$tomcatServ = Get-CimInstance -Class Win32_Service -Filter "Name LIKE 'Tomcat%'"
if($tomcatServ)
{
##### This part is pretty confusing for someone reading your code, if you show us how does
##### RELEASE-NOTES.txt looks we may be able to improve it and simplified it a bit
$path = $tomcatServ.PathName.Substring(0,$tomcatServ.PathName.LastIndexOf("\")-3)
$path = Join-Path $path -ChildPath "webapps\ROOT\RELEASE-NOTES.txt"
$tomCatVer = ((Get-Content $path) -replace ":", "$" | Select-String -Pattern 'Apache Tomcat Version ').TrimStart()
##### This part is a bit confusing too
$key = 'HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\Tomcat9\Parameters\Java'
$javaVer = GetChild-Item -Path ((Get-ItemProperty -Path $key).Jvm).VersionInfo.ProductName
#####
[PSCustomObject]#{
Server_name = $env:ComputerName
Service_name = $tomcatServ.Name
Service_status = $tomcatServ.State
Tomcat_version = $tomCatVer
Java_Version = $javaVer
}
}
}
$Output = Invoke-Command -ComputerName $Servers -ScriptBlock $scriptBlock -HideComputerName
$Output | Select-Object * -ExcludeProperty RunspaceID | Format-Table -AutoSize

Running a powershell from rundeck(linux) display different result

I'm trying to run a powershell script from rundeck(linux), If I run the script locally[Deletes some files from multiple terminal servers](Windows server) it is working as expected however if I call it from rundeck server(winrm configured) it seems that the script cant access the remote folders I'm trying to access.
I tried running the script using the same user but still shows different result.
Script bellow:
$userAD = "someuser"
$servers = Get-Content C:\TSList.csv
$Folder = "c$\Users\$userAD\"
$TSFolderShare = "\\sharepath"
Write-Output "#####Start of script#####"
Write-output `n
Write-output "Checking if $userAD user profile exist in Terminal servers..."
sleep -seconds 1
foreach ($server in $servers) {
Test-Path "\\$server\$Folder" -PathType Any
Get-ChildItem "\\$server\$Folder"
if (Test-Path "\\$server\$Folder" -PathType Any) {
Write-output "Resetting user profile in $server.."
Get-ChildItem "\\$server\$Folder" -Recurse -Force -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
sleep -seconds 1
Write-output "Done."
if( (Get-ChildItem "\\$server\$Folder" | Measure-Object).Count -eq 0)
{
Write-output "Done."
}
}
else
{
Write-output "Resetting user profile in $server.."
sleep -seconds 1
Write-output "User profile does not exist in $server."
#Write-output "\\$server\$Folder does not exist in $server!" -ForegroundColor Red
}
}
EDIT: It seems my problem is when running my script from another script with RunAS.
Below I'm trying to access a folder from another server using ps script, but since I want to integrate this to Rundeck I need to call my ps script from my linux server using python. I did a test running the ps script directly and calling the test path script using another script with RunUs using the same user I used to run the script manually
Scenario 1
Running PS script via separate PS script with RunAS(my_account)
$username = "my_account"
$password = "my_password"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
Invoke-Command -FilePath "C:\testpath.ps1" -Credential $cred -Computer localhost
(C:\testpath.ps1) Content below:
Test-Path "\\server\c$\Users\myaccount\"
result:
Access is denied
+ CategoryInfo : PermissionDenied: (\server\c$\Users\myaccount:String) [Test-Path], UnauthorizedAccessException
+ FullyQualifiedErrorId : ItemExistsUnauthorizedAccessError,Microsoft.PowerShell.Commands.TestPathCommand
+ PSComputerName : localhost
False
Scenario 2
Running C:\testpath.ps1 directly as my_account
Test-Path "\\server\c$\Users\myaccount\"
result:
True
I used session configuration in powershell to solve the issue. This way allows you to tie a credential to a PowerShell session configuration and reuse this configuration for all future connections.
https://4sysops.com/archives/solve-the-powershell-multi-hop-problem-without-using-credssp/
Thanks a lot!
You're facing a double-hop issue with Rundeck and Powershell, here the explanation. That's asked before, take a look a this, and here a good workaround. Also this to solve it.

How to integrate registry edit script with exe execution with VBScript

I am trying to write script which should change the registry settings (WSUS server name) of remote servers. Once success script should execute exe and VBScript file stored on remote servers which installs patches on the server. below is my progress.
Change WSUS settings of the server. This is done, I am able to change WSUS server in registry on remote servers and get the result in a CSV file.
Once above step is successfull my below script should be able to execute mentioned command on remote servers, which is not happening currently.
Script:
#defined variables to use in script as below.##
$Computers = Get-Content "C:\serverlist.txt"
$Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
$property = "WUServer"
$value = "http://serverFQDN"
$results = foreach ($computer in $Computers) {
if (Test-Connection -ComputerName $computer -Count 1 -Quiet) {
try {
Set-ItemProperty -Path $path -Name $Property -Value $Value -ErrorAction 'Stop'
$status = "WSUS server has been set to http://serverFQDN"
foreach ($computer in $Computers) {
# below step is not getting executed on remote servers. I do not
# get any error but above script do not execute any command on
# remote servers.
foreach ($computer in $computers) {
Invoke-Command -ScriptBlock {
C:\test\Patcher > cscript.exe -nologo test_v1.03.vbs /anlyze:false /reboot:false | Out-Null
}
}
}
} catch {
$status = "Failed"
}
} else {
$status = "Server is not reachable, please check connection and try again"
}
New-Object -TypeName PSObject -Property #{
'Computer' = $computer
'Status' = $status
}
}
$results | Export-Csv -NoTypeInformation -Path "./out.csv"
This isn't valid syntax:
C:\test\Patcher > cscript.exe -nologo test_v1.03.vbs ...
The statement would (try to) run a command Patcher located in the directory C:\test and redirect its output to a file cscript.exe in the current working directory.
You probably want to change the working directory to C:\test\Patcher and run test_v1.03.vbs from that directory. To do that change the above line to this:
Set-Location 'C:\test\Patcher'
& cscript.exe //NoLogo test_v1.03.vbs /anlyze:false /reboot:false | Out-Null

Is it possible to invoke-command with in a workflow?

Do anyone know if I can use Invoke-Command in a PowerShell workflow?
Currently I have script that loops through a text file with the list of services but I would like it push to all of the servers at once verses going through one by one. Is this possible?
This is the current script block I am working with:
{
ForEach ($Server in $Servers) {
Write-Host "Copying code to $Server..."
If (!(Test-Path -path \\$Server\c$\Websites\Versions\v$version)) {
New-Item \\$Server\c$\Websites\Versions\v$version -Type Directory | Out-Null
}
Copy-Item .\Packages\v$version\* \\$Server\c$\Websites\Versions\v$version -Force -Recurse
Write-Host "Converting to application on $Server..."
Invoke-Command -ComputerName $Server -ScriptBlock $Script -Argumentlist $Version | Out-Null
}
}
The PowerShell Workflow engine is not capable of directly invoking PowerShell cmdlets. Instead, if a script writer calls a PowerShell cmdlet inside a Workflow definition, the PowerShell Workflow engine will automatically wrap invocations of PowerShell cmdlets inside the InlineScript Workflow Activity.
workflow test
{
ForEach ($Server in $Servers) {
Write-Host "Copying code to $Server..."
If (!(Test-Path -path \\$Server\c$\Websites\Versions\v$version)) {
New-Item \\$Server\c$\Websites\Versions\v$version -Type Directory | Out-Null
}
Copy-Item .\Packages\v$version\* \\$Server\c$\Websites\Versions\v$version -Force -Recurse
Write-Host "Converting to application on $Server..."
InlineScript {
Invoke-Command -ComputerName $Server -ScriptBlock $Script -Argumentlist $Version | Out-Null
}
}
}
As for whether or not it will work, you'll have to try it out, as suggested by Mathias.
#Trevor's response is good as an overall skeleton, but it won't work as it is.
There are several things missing or incorrect:
Passing arguments to workflow
Passing arguments to InlineScript
Passing ScriptBlock as an argument;
Using Out-Null in workflow
The working example:
$serversProd=#"
server1
server2
server3
server4
"#-split'\r\n'
$reportScript = "report.script.ps1"
$generateReport = {
param($reportScript)
cd D:\Automations\ConnectivityCheck
powershell -file $reportScript
}
workflow check-connectivity {
Param ($servers, $actionBlock, $reportScript)
# Prepare the results folder
$resultsFolder = "D:\Automations\ConnectivityCheckResults"
$unused1 = mkdir -Force $resultsFolder
# Run on all servers in parallel
foreach -parallel ($server in $servers) {
# Upload script to the server
$unused2 = mkdir -Force \\$server\D$\Automations\ConnectivityCheck
cp -Force $reportScript \\$server\D$\Automations\ConnectivityCheck\
"Starting on $server..."
# Execute script on the server. It should contain Start-Transcript and Stop-Transcript commands
# For example:
# $hostname = $(Get-Wmiobject -Class Win32_ComputerSystem).Name
# $date = (Get-Date).ToString("yyyyMMdd")
# Start-Transcript -path ".\$date.$hostname.connectivity.report.txt"
# ...Code...
# Stop-Transcript
$results = InlineScript {
$scriptBlock = [scriptblock]::Create($Using:actionBlock)
Invoke-Command -computername $Using:server -ScriptBlock $scriptBlock -ArgumentList $Using:reportScript
}
# Download transcript file from the server
$transcript = [regex]::Match($results,"Transcript started.+?file is \.\\([^\s]+)").groups[1].value
"Completed on $server. Transcript file: $transcript"
cp -Force \\$server\D$\Automations\ConnectivityCheck\$transcript $resultsFolder\
}
}
cls
# Execute workflow
check-connectivity $serversProd $generateReport $reportScript

Resources