mount.exe in powershell service not mounting NFS for the user, only current process - windows

I'm using winsw to run a powershell 7 service with user credentials, in order to automatically mount an NFS volume. I can verify the service is running as that user since $env:UserName shows up correctly in the log.
Strangely, when the service runs this command:
mount.exe -o anon,nolock,hard 10.1.132.244:/rendering.dev.firehawkvfx.com X:
The service script can see the contents of the mounted path and that works, but the user in the windows UI session cannot, and the mount doesn't arrive in windows explorer at all. It appears the mount only exists for the process. This must have something to do with the way processes are isolated in windows is my guess.
There are a few components involved in doing this, but at the risk of being verbose the winsw service looks like this:
<service>
<id>myservice</id>
<name>MyService</name>
<description>This service updates Deadline Certificates with Firehawk.</description>
<serviceaccount>
<username>.\REPLACE_WITH_DEADLINE_USER_NAME</username>
<password>REPLACE_WITH_DEADLINE_USER_PASS</password>
<allowservicelogon>true</allowservicelogon>
</serviceaccount>
<env name="FH_DEADLINE_CERTS_HOME" value="%BASE%"/>
<executable>C:\Program Files\PowerShell\7\pwsh.exe</executable>
<startarguments>-NoLogo -ExecutionPolicy Bypass -File c:\AppData\myservice.ps1</startarguments>
<log mode="roll"></log>
</service>
and myservice.ps1 wrapper that runs the NFS mount.exe command (in aws-auth-deadline-pwsh-cert.ps1) looks like this:
#Requires -Version 7.0
Write-Host "Start Service"
# $ErrorActionPreference = "Stop"
function Main {
$Timer = New-Object Timers.Timer
$Timer.Interval = 10000
$Timer.Enabled = $True
$Timer.AutoReset = $True
$objectEventArgs = #{
InputObject = $Timer
EventName = 'Elapsed'
SourceIdentifier = 'myservicejob'
Action = {
try {
$resourcetier = "dev"
Write-Host "Run aws-auth-deadline-cert`nCurent user: $env:UserName"
Set-strictmode -version latest
if (Test-Path -Path C:\AppData\myservice-config.ps1) {
. C:\AppData\myservice-config.ps1
C:\AppData\aws-auth-deadline-pwsh-cert.ps1 -resourcetier $resourcetier -deadline_user_name $deadline_user_name -aws_region $aws_region -aws_access_key $aws_access_key -aws_secret_key $aws_secret_key
}
else {
Write-Warning "C:\AppData\myservice-config.ps1 does not exist. Install the service again and do not use the -skip_configure_aws argument"
}
Write-Host "Finished running aws-auth-deadline-cert"
}
catch {
Write-Warning "Error in service Action{} block"
Write-Warning "Message: $_"
exit(1)
}
}
}
$Job = Register-ObjectEvent #objectEventArgs
Wait-Event
}
try {
Main
}
catch {
Write-Warning "Error running Main in: $PSCommandPath"
exit(1)
}
In case its of interest, I maintain this work ongoing at this github repo - https://github.com/firehawkvfx/firehawk-auth-scripts

Related

Powershell - Loop Install of Available Software Updates (SCCM)

I have the below script which I am using to run on critical desktop clients to install all available updates (quarterly) that have been deployed by SCCM.
As some deployed updates only become available when other dependent updates have been installed the script is stopping before the reboot.
I ideally want it to loop and continue to install all available updates until all have installed and then proceed to automatically reboot.
Any ideas?
Add-Type -AssemblyName PresentationCore, PresentationFramework
switch (
[System.Windows.MessageBox]::Show(
'This action will download and install critical Microsoft updates and may invoke an automatic reboot. Do you want to continue?',
'WARNING',
'YesNo',
'Warning'
)
) {
'Yes'
{
Start-Process -FilePath "C:\Windows\CCM\ClientUX\scclient.exe" "softwarecenter:Page=InstallationStatus"
$installUpdateParam = #{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdatesManager'
MethodName = 'InstallUpdates'
}
$getUpdateParam = #{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdate'
Filter = 'EvaluationState < 8'
}
[ciminstance[]]$updates = Get-CimInstance #getUpdateParam
if ($updates) {
Invoke-CimMethod #installUpdateParam -Arguments #{ CCMUpdates = $updates }
while(Get-CimInstance #getUpdateParam){
Start-Sleep -Seconds 30
}
}
$rebootPending = Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending
if ($rebootPending.RebootPending){
Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName RestartComputer
}
'No'
# Exit-PSSession
}
}
You may loop indefinitely to start the process and stop only when $updates is $null or empty.
while($true) {
Start-Process ...
[ciminstance[]]$updates = Get-CimInstance #getUpdateParam
if ($updates) {
Invoke-CimMethod #installUpdateParam -Arguments #{ CCMUpdates = $updates }
while(Get-CimInstance #getUpdateParam){
Start-Sleep -Seconds 30
}
}
else {
break;
}
}

Powershell: Find the currently logged on user and copy a unique file from a shared drive

Title says most of it. I have generated unique files for users who will be running their scripts remotely. The script is supposed to find the name of the currently logged on user and copy that unique file to C:\Users\Public. Currently however I am running into an issue where the system seems to default to my username. I have tried multiple methods sourced from here and stack overflow and cannot seem to get a good result, as everyone ends up with my unique file. I have tried the following:
$env:username
$env:userprofile
$currentuser=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
The script looks as such:
$currentuser=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
if ($currentuser = "Domain/username1") {
copy-item -Path "shared network location\username1file" -Destination "C:\Users\Public"
}
elseif ($currentuser = "Domain\username2") {
copy-item -Path "shared network location\username2file" -Destination "C:\Users\Public"
}
elseif ($currentuser = "domain\username3") {
copy-item -Path "shared network location\username3file" -Destination "C:\Users\Public"
}
Can anyone provide me any advice on how to fix this?
To get the currently logged on user and not the user currently running the script, you can use WMI Win32_ComputerSystem.
Also, I would recommend using switch instead of multiple elseif:
$currentuser = (Get-WmiObject -Class Win32_ComputerSystem).UserName
$file = switch ($currentuser) {
'Domain\username1' { "shared network location\username1file"; break }
'Domain\username2' { "shared network location\username2file"; break }
'Domain\username3' { "shared network location\username3file"; break }
default { $null }
}
if ($file) {
Copy-Item -Path $file -Destination "C:\Users\Public"
}
else {
Write-Host "No file to copy defined for user '$currentuser'"
}

appcmd.exe set config doesn't check if username or password is invalid and sets it anyways

I'm using winexe from my backend api to run commands on Windows Domain Server. I want to set IIS App Pool Identity as an Account from Active Directory. The problem is that while using this command :
%windir%\system32\inetsrv\appcmd.exe set config /section:applicationPools ^
/[name='POOLNAME'].processModel.identityType:SpecificUser ^
/[name='POOLNAME'].processModel.userName:DOMAIN\USER ^
/[name='POOLNAME'].processModel.password:PASSWORD
It runs successfully everytime even if the username and password is incorrect. Even the pool gets Started with wrong password. However setting wrong password through GUI fails.
I want to identify when the password or username is being set wrongly.
PS: I even tried using Set-ItemProperty on powershell and the result was the same.
You can't test your credentials with AppPool, but you can definitely test them.
# Service Principal credentials
$username = 'Username'
$password = 'Password' | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList $username, $password
if (Test-Credential -Credential $credential) {
Write-Verbose "Credentials for $($credential.UserName) are valid..."
# do the appcmd stuff
}
else {
Write-Warning 'Credentials are not valid or some other logic'
}
Just add Test-Credential function definition at the top of your script
function Test-Credential {
[CmdletBinding()]
Param
(
# Specifies the user account credentials to use when performing this task.
[Parameter()]
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$Credential = [System.Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$DS = $null
$Username = $Credential.UserName
$SplitUser = $Username.Split('\')
if ($SplitUser.Count -eq 2 ) {$Username = $SplitUser[1]}
if ($SplitUser.Count -eq 1 -or $SplitUser[0] -eq $env:COMPUTERNAME ) {
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('machine', $env:COMPUTERNAME)
}
else {
try {
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('domain')
}
catch {
return $false
}
}
$DS.ValidateCredentials($Username, $Credential.GetNetworkCredential().Password)
}
(PS: Code is valid even though prettifier break with backslash quote syntax)
amazingly i puzzled out that you can do it like this - but it still doesn't validate
appcmd set apppool junkapp /processmodel.password:junkpassword

Powershell Delete Profile script - error checking not working

I have this delete profile script that prompts for a username and deletes it from each of the computers listed. The delete profile and "user is logged in" parts are both working but the part that says “No profiles found on $Computer with Name $UserName” is not. I ran my script on two computers and it successfully deleted my profile on both. I recreated my profile (logged in) and stayed logged on to one and not the other. I run it again and it gives me the message "user is logged in". For the other computer it just deleted the profile on does not display the "no profile found" message. It just skips over it and displays nothing. I have changed the "if" to an "else" but, when I do that it displays multiple lines of "no profiles found" including the computer it previously deleted the profile on.
Here is the link where most of the script is derived from.
http://techibee.com/powershell/powershell-script-to-delete-windows-user-profiles-on-windows-7windows-2008-r2/1556. Looking through the comments, no one else seemed to have any issues with that part of it.
I do not have much knowledge in PowerShell and this has just been pieced together from other scripts I have found based on our needs. Our environment is Windows 7 and Server 2008 R2. Any assistance is greatly appreciated.
$UserName=Read-host "Please Enter Username: "
$ComputerName= #("computer1","computer2")
foreach($Computer in $ComputerName) {
Write-Verbose "Working on $Computer"
if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) {
$Profiles = Get-WmiObject -Class Win32_UserProfile -Computer $Computer -ea 0
foreach ($profile in $profiles) {
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$profilename = $objuser.value.split("\")[1]
if($profilename -eq $UserName) {
$profilefound = $true
try {
$profile.delete()
Write-Host -ForegroundColor Green "$UserName profile deleted successfully on $Computer"
} catch {
Write-Host -ForegroundColor Yellow "Failed to delete the profile, $UserName logged on to $Computer"
}
}
}
if(!$profilefound) {
Write-Host -ForegroundColor Cyan "No profiles found on $Computer with Name $UserName"
}
} else {
write-verbose "$Computer Not reachable"
}
}
PowerShell has a number of automatic variables that you should avoid re-using.
$Profile is one of these, it contains the paths to the Profile scripts applicable to the current session.
Use any other variable name (ie. $userprofile) and you'll be fine:
foreach ($userprofile in $profiles) {
$objSID = New-Object System.Security.Principal.SecurityIdentifier($userprofile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$profilename = $objuser.value.split("\")[1]
if($profilename -eq $UserName) {
$profilefound = $true
try {
$userprofile.delete()
Write-Host -ForegroundColor Green "$UserName profile deleted successfully on $Computer"
} catch {
Write-Host -ForegroundColor Yellow "Failed to delete the profile, $UserName logged on to $Computer"
}
}
}
I was able to get it working by changing the "$profilefound=$false" and making it a global variable. Also the reason why it was displaying multiple lines of "profile not found when i changed it to an else statement is because of where it was placed. It was checking against every profile on the server. When it touched every profile on the computer it displayed "profile not found".
Here is the working script.
$UserName=Read-host "Please Enter Username: "
$ComputerName= #("computer1","computer2")
$profilefound = "false"
foreach($Computer in $ComputerName) {
Write-Verbose "Working on $Computer"
if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) {
$Profiles = Get-WmiObject -Class Win32_UserProfile -Computer $Computer -ea 0
foreach($userprofile in $profiles){
$objSID = New-Object System.Security.Principal.SecurityIdentifier($userprofile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$profilename = $objuser.value.split("\")[1]
if($profilename -eq $UserName) {
$profilefound = "true"
try {
$userprofile.delete()
Write-Host -ForegroundColor Green "$UserName profile deleted successfully on $Computer"
} catch {
Write-Host -ForegroundColor Yellow "Failed to delete the profile, $UserName logged on to $Computer"
}
}
}
}
else {
write-verbose "$Computer Not reachable"
}
if ($profilefound -eq "false") {
Write-Host -ForegroundColor Cyan "No profiles found on $Computer with Name $UserName"
}
}

Logstash-forwarder as Windows Service

I'm struggling to create a Windows Service for a logstash forwarder on Windows 2008 R2 Server.
My setup looks as follows:
Ubuntu Server 14.04 LTS
Elasticsearch
Logstash
Kibana
Windows Server 2008 R2:
Application logging to a certain path.
Ship logs to the ELK Stack via Logstash-forwarder
I'm currently shipping logs successfully to the ELK-Stack via Logstash forwarder compiled for Windows using the instructions here... https://github.com/elastic/logstash-forwarder. The only problem is, that I have to run the logstash forwarder in a CLI window, and I'm not able to set it up as a Windows Service.
I've tryed the following SC command, the service is created but the service will not start at all. Just returning the following error: The service did not respond to the start or control request in a timely fashion.
sc create LogstashForwarder binpath= "\"C:\_Logstash\logstash-forwarder.exe\" -config=\"C:\_Logstash\logstash-forwarder.conf\"" start= Auto displayname= "Logstash forwarder"
Unfortunately Google does not know any answer either.
Does anyone have been able to start the logstash forwarder on Windows as a Windows Service with the SC command? Some good advice will be very appreciated.
If your logstash configuration is correct try these steps.
Get nssm soft
Decompress the nssm zip in the bin folder of logstash
Excecute from command line nssm install logstash
Add the path to your bat on the launched config screen
Add your startup directory too.
Here you can get some more help
https://blog.basefarm.com/blog/how-to-install-logstash-on-windows-server-2012-with-kibana-in-iis/
https://github.com/verbosemode/public-notes/blob/master/logstash-windows.md
Hope this help
To Add to Rys' answer, Logstash-Forwarder doesn't natively read the Windows Event log. Whilst looking into how to get around this I came across this gist by Sean-M.
I modified his original script so that the Powershell script starts LSF and then pipes the event log into the stdin. I then point NSSM at the script and run that as a service. If you have your configuration file setup like this:
{
"network": {
"servers": [ "<logstash IP>:5000" ],
"timeout": 15,
"ssl ca": "C:/path/to/logstash-forwarder.crt"
},
"files": [
{
"paths": [
"C:/inetpub/logs/LogFiles/W3SVC*/*.log"
],
"fields": { "type": "iis-w3svc" }
},
{
"paths": [
"-"
],
"fields": { "type": "windows-event" }
}
]
}
LSF will capture the JSON input and send it to Logstash. Powershell code below**:
#Requires -Version 3
param (
[string]$lognames
)
#reading security log requires elevated privileges so only read Application and System for now
[string[]]$logname = $("Application", "System" )
if ($lognames)
{
[string[]]$logname = $lognames -split ", "
}
##################################
# Functions #
##################################
function EvenSpace{
param ($word)
$tabWidth = 48
$wordTabs = $tabWidth - $word.Length
$tabNum = [Math]::Floor($($wordTabs/4)) / 2
("`t" * $tabNum)
}
## Read events, write to file
function ReadEvents {
param ([hashtable]$filter, [string]$OutFile=[String]::Empty)
## Make it look pretty if writting to stdout
try {
[object[]]$data = Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue | sort RecordId
[int]$count = 0
if ((-not $data -eq $null) -or ($data.Count -gt 0)) {
$count = $data.Count
}
Write-Verbose ("Log: $($filter["LogName"])" + (EvenSpace -word $filter["LogName"]) + "Count: $count")
}
catch {
$Error[0]
Write-Verbose ""
Write-Verbose "Filter:"
$filter
return
}
if ($data.Count -gt 0) {
foreach ($event in $data) {
$json = $event | ConvertTo-Json -Compress
#$jsonbytes = #($json)
#$process.StandardInput.BaseStream.Write($jsonbytes,0,$jsonbytes.Count)
Write-Verbose $json
$process.StandardInput.WriteLine($json)
}
}
}
## Use a try/catch/finally to allow for the inputs to be closed and the process stopped
[System.Diagnostics.Process]$process = $null
$endTime = Get-Date
try
{
## Prepare to invoke the process
$processStartInfo = New-Object System.Diagnostics.ProcessStartInfo
$processStartInfo.FileName = (Get-Command .\logstash-forwarder.exe).Definition
$processStartInfo.WorkingDirectory = (Get-Location).Path
$processStartInfo.Arguments = "-config logstash-forwarder.conf"
$processStartInfo.UseShellExecute = $false
## Always redirect the input and output of the process.
## Sometimes we will capture it as binary, other times we will
## just treat it as strings.
$processStartInfo.RedirectStandardOutput = $true
$processStartInfo.RedirectStandardInput = $true
$process = [System.Diagnostics.Process]::Start($processStartInfo)
##################################
# Main Logic #
##################################
## Loop to capture events
while ($true) {
[String]::Empty | Write-Verbose
Start-Sleep -Seconds 5
$startTime = $endTime
[TimeSpan]$diff = (Get-Date) - $startTime
if ($diff.TotalHours -gt 1) {
$endTime = $startTime + (New-TimeSpan -Minutes 30)
}
else {
$endTime = Get-Date
}
Write-Verbose "Starting timespan $($startTime) -> $($endTime)"
## Supports reading multiple logs
if ($logname.Count -gt 1) {
foreach ($log in $logname) {
ReadEvents -filter #{LogName=$log; StartTime=$startTime; EndTime=$endTime} -OutFile $output
}
}
else {
ReadEvents -filter #{LogName=$logname; StartTime=$startTime; EndTime=$endTime} -OutFile $output
}
}
}
catch
{
Write-Error $error[0]|format-list -force
throw $_.Exception
}
finally
{
if($process)
{
$process.StandardInput.Close()
$process.Close()
}
}
** The script doesn't really handle LSF failing, but it serves my purposes for now.
Tried to add this under the Answer using nssm. You can also use the following to create the service from commandline without the UI.
Just ensure that nssm.exe is in the same directory and you run the script from there (or just edit the script).
#echo off
set BASE_DIR=C:\temp\logstash-forwarder
nssm install Logstash-Forwarder "%BASE_DIR%\logstash-forwarder.exe"
nssm set Logstash-Forwarder AppDirectory "%BASE_DIR%"
nssm set Logstash-Forwarder AppStopMethodSkip "6"
nssm set Logstash-Forwarder AppParameters "-config %BASE_DIR%\logstash-forwarder.conf"

Resources