I have written a function that writes log files for my scripts. The first time the function is used, it writes a log file in the directory and the name of the script. Every subsequent run, log messages are attached to the file.
So far, so good. Against all odds, other people are starting to use my scripts now! The scripts are mainly used by administrators with local admin rights on servers. But they all get errors when writing to the log file. To my understanding, when you access files with rights provided by the „Administrators“ group, you must be in elevated privilege mode. But I don’t want that. I manually tried to assign modify to the „Users“ group, but then „Administrators“ seem to take precedence.
Anyone any idea what rights to set (and/or to revoke) and how to achieve this in PowerShell?
As Ansgar Wiecher comments, you should probably look into using the Event Log service instead.
With event logs, you only need elevated privileges the first time one of the scripts run, in order to create the log and register the event source, after that anyone can write to it:
function Write-MyLog {
param(
[Parameter(Mandatory = $true)]
[string]$Message,
[Parameter(Mandatory = $true)]
[ValidateRange(1,65535)]
[int]$EventId,
[Parameter(Mandatory = $false)]
[System.Diagnostics.EventLogEntryType]$EntryType = 'Information'
)
# Prepend PID and script path to message
$PSBoundParameters['Message'] = '[{0}: {1}]{2}{3}' -f $PID,$MyInvocation.ScriptName,[Environment]::NewLine,$Message
# Set event log target
$PSBoundParameters['LogName'] = $logName = 'LeosEvents'
$PSBoundParameters['Source'] = $logSource = 'LeosScripts'
if (-not (Get-WinEvent -ListLog $logName -ErrorAction SilentlyContinue)) {
# Create event log and source if it doesn't exist already
# This is the only step that requires elevation, can be created via GPO if desired
New-EventLog -LogName $logName -Source $logSource
}
# Write event log entry
Write-EventLog #PSBoundParameters
}
Related
I have to following part of my script:
$active_processes = (Get-WmiObject -Class Win32_Process | where path -like $path | Select-Object -ExpandProperty Path | split-path -leaf | Select-Object -Unique)
It's working fine but I need to check if the process I get after all the script is running with elevated rights to launch another process with elevated rights if neccesary so it can interact with said process. I don't see any information about elevated rights with Get-WmiObject, I was wondering if I'm missing it or if there's another way to get that information
I don't need to run the powershell script as administrator. What I need is to find ff any executable requires elevated rights when launched and I need to find this information via powershell.
After some research on how windows knows if it needs admin to run an executable, I concluded that there are a couple ways but the most recommended and reliable is reading the executable manifest, so I wrote the following function:
function Get-ManifestFromExe{
Param(
[Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true)]
[Alias("Path")]
[ValidateScript({Test-Path $_ -IsValid})]
[String]$FullName
)
begin{
$stringStart = '<assembly'
$stringEnd = 'assembly>'
}
process{
$content = Get-Content $FullName -Raw
$indexStart = $content.IndexOf($stringStart)
$content = $content.Substring($indexStart)
$indexEnd = ($content.IndexOf($stringEnd)) + $stringEnd.Length
$content = $content.Substring(0,$indexEnd)
if($content -match "$stringStart(.|\s)+(?=$stringEnd)$stringEnd"){
return [XML]$Matches[0]
}
}
}
function Test-IsAdminRequired{
Param(
[Parameter(Mandatory=$true,Position=0)]
[XML]$xml
)
$value = $xml.assembly.trustInfo.security.requestedPrivileges.requestedExecutionLevel.level
if(-not [String]::IsNullOrEmpty($value)){
return ($value -eq "requireAdministrator" -or $value -eq "highestAvailable")
}else{
Write-Error "Provided xml does not contain requestedExecutionLevel node or level property"
}
}
$exe = '.\Firefox Installer.exe'
Get-ManifestFromExe -Path $exe
Test-IsAdminRequired -xml $exeManifest
It works by extracting the manifest XML from the executable and checking requestedExecutionLevel node's level property, the values accepted for this property are in this page, and quoted here:
asInvoker, requesting no additional permissions. This level requires
no additional trust prompts.
highestAvailable, requesting the highest permissions available to the
parent process.
requireAdministrator, requesting full administrator permissions.
So from this we can conclude that only highestAvailable and requireAdministrator would need admin privileges, so I check those, with that we will be done EXCEPT that some executables I tested (mostly installers) don't require admin to run but instead they prompt the UAC when they ruin their child executable, I don't really see a way to check this.. sorry.
BTW I really enjoyed this question (specially the research), hope this can help you.
SOURCES
What is the difference between "asInvoker" and "highestAvailable" execution levels?
reading an application's manifest file?
https://learn.microsoft.com/en-us/visualstudio/deployment/trustinfo-element-clickonce-application?view=vs-2019#requestedexecutionlevel
It's in the System.Security.Principal classes. This returns $true if the current user is elevated to local Administrator:
(New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
I have some problems getting EventLog and save data. I am able to get my EventLogs but not logs from network computers.
Here is the code I am running:
$logFileName = "Application"
$path = $MyInvocation.MyCommand.Path +"\Output\"
$path = $PSScriptRoot+"\Output\"
new-item $path -ItemType directory
$array = ("System", "Security")
$file = $PSScriptRoot +"\computers.txt"
$users = ForEach ($machine in $(Get-Content $file)) {
$pathMachine = $path+$machine
new-item $pathMachine -ItemType directory
ForEach ($logFileName in $array){
# do not edit
$logFileName
$exportFileName = (get-date -f yyyyMMdd) + "_" + $logFileName + ".evt"
$logFile = Get-WmiObject Win32_NTEventlogFile -ComputerName $machine | Where-Object {$_.logfilename -eq $logFileName}
$logFile
$exportFileName
$pathMachine
$temp = $pathMachine + "\"+ $exportFileName
$temp
$fff = $logFile.BackupEventLog($temp)
}
}
This could e considered a duplicate of this.
Reading event log remotely with Get-EventLog in Powershell
# swapped from this command
get-eventlog -LogName System -computername <ServerName>
# to this
invoke-command {get-eventlog -LogName System} -ComputerName <ServerName>
Don't struggle with writing this from scratch. Well, unless it's a learning exercise. There are pre-built script for you to leverage as is and or tweak as needed.
Running commands on Remote host require using the Invoke cmdlet, and or an established PSRemoting session to that host.
Get Remote Event Logs With Powershell
Gather the remote event log information for one or more systems using wmi, alternate credentials, and multiple runspaces. Function supports custom timeout parameters in case of wmi problems and returns Event Log information for the specified number of past hours.
Download: Get-RemoteEventLogs.ps1
The script is too long (it's 100+ lines) to post here, but here in the Synopsis of it.
Function Get-RemoteEventLogs
{
<#
.SYNOPSIS
Retrieves event logs via WMI in multiple runspaces.
.DESCRIPTION
Retrieves event logs via WMI and, if needed, alternate credentials. This function utilizes multiple runspaces.
.PARAMETER ComputerName
Specifies the target computer or comptuers for data query.
.PARAMETER Hours
Gather event logs from the last number of hourse specified here.
.PARAMETER ThrottleLimit
Specifies the maximum number of systems to inventory simultaneously
.PARAMETER Timeout
Specifies the maximum time in second command can run in background before terminating this thread.
.PARAMETER ShowProgress
Show progress bar information
.EXAMPLE
PS > (Get-RemoteEventLogs).EventLogs
Description
-----------
Lists all of the event logs found on the localhost in the last 24 hours.
.NOTES
Author: Zachary Loeber
Site: http://www.the-little-things.net/
Requires: Powershell 2.0
Version History
1.0.0 - 08/28/2013
- Initial release
#>
Or this one.
PowerShell To Get Event Log of local or Remote Computers in .csv file
This script is handy when you want to extract the eventlog from remote or local machine. It has multiple filters which will help to filter the data. You can filter by logname,event type, source etc. This also have facility to get the data based on date range. You can change th
Download : eventLogFromRemoteSystem.ps1
Again, too big to post here because the length is like the other one.
I am working on some assumptions but maybe this will help.
When I Ran your Code I got
Get-Content : Cannot find path 'C:\computers.txt' because it does not exist.
I had to make the C:\computers.txt file, then I ran your code again and got this error.
Get-Content : Cannot find path 'C:\Output\computers.txt' because it does not exist.
I made that file in that location, then I ran your code again and I got the event log file. Maybe try creating these two missing files with a command like
Get-WmiObject Win32_NTEventlogFile -ComputerName $machine
mkdir C:\Output\$machine
$env:computername | Out-File -FilePath c:\Output\Computers.txt
You may also want to setup a Network share and output to that location so you can access the event logs from a single computer. Once the share is setup and the permissions just drop the unc path in.
I am trying to run a PowerShell script daily through task scheduler, however the script will not run. When I enter the code below manually into PowerShell (as an administrator), it makes me press enter twice. I believe since i have to press enter twice is the reason it will not run through the task scheduler.
Is there a way to adjust my code to get this to work with the task scheduler?
I am running Windows 2012 R2 and Version 5.1 of PowerShell.
Please note that i ran the exact same script on my computer, which is Windows 10 and running version 5.1 of PowerShell, and it worked the correct way (only had to press enter once)
I expect to only press enter once to run my PowerShell script, but the actual output from the first time i press enter brings another line with just ">>" and then i press enter the second time and the script executes.
Powershell Script:
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = ""
UserName = ""
Password = ""
SshHostKeyFingerprint = ""
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Transfer files
$session.PutFiles("", "").Check()
}
finally
{
$session.Dispose()
}
If a script requires user interaction, then it really should not be a scheduled task.
If you write a script that requires confirmation, then you need to look using -Confirm parameter.
See Are you sure? Using the -WhatIf and -Confirm parameters in PowerShel
Remove-MailContact -Identity “$sourceEmail” -Confirm:$Y -WhatIf
The cmdlet or code you write has to support it. For code you write, that means using advanced functions.
How to write a PowerShell function to use Confirm, Verbose and WhatIf
function Set-FileContent
{
[cmdletbinding(SupportsShouldProcess)]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Content,
[Parameter(Mandatory = $true)]
[ValidateScript( {Test-Path $_ })]
[string]$File
)
if ($PSCmdlet.ShouldProcess("$File" , "Adding $Content to "))
{
Set-Content -Path $File -Value $Content
}
}
See also ConfirmPreference
I have a very basic function so sync a remote folder to local. This is working well with a FileTransferred handler and I wanted to add a FileTransferProgress to the mix and then use Write-Progress. However I cannot get to that since it appears I cannot add a FileTransferProgress handler while the session is open.
function Sync-RemoteToLocalFolder{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[WinSCP.Session]$Session,
[Parameter(Position=0)]
[string]$RemotePath="/",
[Parameter(Position=1,mandatory)]
[string]$LocalPath
)
$fileTransferedEvent = {Invoke-FileTransferredEvent $_}
# Determine how many files are in this folder tree
$fileCount = (Get-WinSCPItems -Session $Session -RemotePath $RemotePath | Where-Object{$_.IsDirectory -eq $false}).Count
$fileProgressEvent = {Invoke-FileProgressEvent $_ $fileCount}
try{
# Set the file transfer event handler
Write-Verbose "Setting Transfered handler"
$session.add_FileTransferred($fileTransferedEvent)
# Set the transfer progress handler
Write-Verbose "Setting Progress handler"
$Session.add_FileTransferProgress($fileProgressEvent)
# Sync the directories
Write-Verbose "Syncronizing '$LocalPath' with '$RemotePath'"
$synchronizationResult = $session.SynchronizeDirectories([WinSCP.SynchronizationMode]::Local, $LocalPath, $RemotePath, $False)
# Check the result for errors
$synchronizationResult.Check()
# Remove the handlers from the session
$session.remove_FileTransferred($fileTransferedEvent)
$Session.remove_FileTransferProgress($fileProgressEvent)
}catch [Exception]{
Write-Error $_
}
}
If I run this, with an open $session passed then I get the message Sync-RemoteToLocalFolder : Session is already opened. I found that odd since I added a different kind of handler on the fly but these could function differently. So I can comment out the two lines about the FileTransferProgress and the above function works as much as I want it to (there are some logic flaws but they exist outside of this issue e.g. I need to update the scriptblock for $fileProgressEvent).
Why can I not add a FileTransferProgress handler while the session is open?
It's a limitation of the implementation.
Nothing you can do about it.
It's documented now:
The event has to be subscribed before calling Open.
As a workaround, you can introduce a flag in the event handler, to turn it off.
I'm running a Windows Service (Hudson) which in turn spawns a PowerShell process to run my custom PowerShell commands. Part of my script is to unzip a file using CopyHere. When I run this script locally, I see a progress dialog pop up as the files are extracted and copied. However, when this runs under the service, it hangs at the point where a dialog would otherwise appear.
Here's the unzip portion of my script.
# Extract the contents of a zip file to a folder
function Extract-Zip {
param([string]$zipFilePath, [string]$destination)
if(test-path($zipFilePath)) {
$shellApplication = new-object -com shell.application
$zipFile = get-item $zipFilePath
$zipFolder = $shellApplication.NameSpace($zipFile.fullname)
$destinationFile = get-item $destination
$destinationFolder = $shellApplication.NameSpace($destinationFile.fullname)
$destinationFolder.CopyHere($zipFolder.Items())
}
}
I suspect that because its running under a service process which is headless (no interaction with the desktop), its somehow stuck trying to display a dialog.
Is there a way around this?
If it's still actual, I managed to fix this with having CopyHere params equal 1564.
So in my case extract zip function looks like:
function Expand-ZIPFile{
param(
$file, $destination
)
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)
foreach($item in $zip.items())
{
$shell.Namespace($destination).copyhere($item,1564)
"$($item.path) extracted"
}
1564 description can be found here - http://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx:
(4) Do not display a progress dialog box.
(8) Give the file being operated on a new name in a move, copy, or rename operation if a file with the target name already exists.
(16) Respond with "Yes to All" for any dialog box that is displayed.
(512) Do not confirm the creation of a new directory if the operation requires one to be created.
(1024) Do not display a user interface if an error occurs.
If this is running on Vista or Windows 7, popping up UI from a service isn't going to be seen by the end user as you suspected. See this paper on Session 0 Isolation. However, does the progress dialog require user input? If not, I wouldn't think that would cause the service to hang. I would look for an option to disable the progress display. If you can't find that, then try switching to another ZIP extractor. PSCX 1.2 comes with an Expand-Archive cmdlet. I'm sure there are also others available.
Looking at the documentation for PowerShell, it looks like the -NonInteractive option may help here