I am trying to write something that will prompt for a PC name and then remotely perform a folder permission change. Everything is chaotic looking but I am struggling with changing the execution policy after the Enter-PSSession - it seems it just gets stuck and won't proceed further to my function.
This is the rough draft of what I have thus far. Any help is appreciated.
$PromptedPC = Read-Host -Prompt "`n `n Enter PC Number"
Enter-PSSession -ComputerName $PromptedPC
Write-Host `n"Do you want to change folder permissions?" -ForegroundColor Green
$ReadAnswer = Read-Host " ( y / n ) "
PowerShell.exe -noprofile -ExecutionPolicy Bypass
switch ($ReadAnswer)
{
Y {
function Grant-userFullRights {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)]
[string[]]$Files,
[Parameter(Mandatory=$true)]
[string]$UserName
)
$rule=new-object System.Security.AccessControl.FileSystemAccessRule ($UserName,"FullControl","Allow")
foreach($File in $Files) {
if(Test-Path $File) {
try {
$acl = Get-ACL -Path $File -ErrorAction stop
$acl.SetAccessRule($rule)
Set-ACL -Path $File -ACLObject $acl -ErrorAction stop
Write-Host "Successfully set permissions on $File"
} catch {
Write-Warning "$File : Failed to set perms. Details : $_"
Continue
}
} else {
Write-Warning "$File : No such file found"
Continue
}
}
}
}
}
Grant-userFullRights -Files 'C:\ProgramData\New World Systems\' -UserName "BUILTIN\Users"
I did get the function information from: http://techibee.com/powershell/grant-fullcontrol-permission-to-usergroup-on-filefolder-using-powershell/2158
I am just trying to put something together that will work for a programdata folder our users need full control of that keeps reverting permissions to something lower than full access.
You start a new PowerShell with the ExecutionPolicy Bypass.
Sine you are already in PowerShell, the proper way to configure the Execution Polcy is Set-ExecutionPolicy.
However, since you don't invoke any script file, there is no need to change the Execution Policy.
Related
I hope someone can help me with the following script:
The script I built works for 1 computer. It asks the user to enter the computer name, and It will check if the computer is ONLINE, if it is it will copy and install the application.
Now, I want to add another functionality. I want users to create a TXT file with all the computer names (line by line) and save them somewhere in the computer. The user will run the script, and a Windows Explorer would open requesting the location of the TXT file. So, the script will collect that data (I might be wrong, but should I use arrays for that?) From there, I am assuming a ForEach statement should do it right?
The most difficult (for me) part is... if the computer is OFFLINE, it will skip that computer, and go for the next one... when it reaches the end of the lines, it should go back to the first line and check if the computer (that was offline) is online. The tricky part is... if the computer already has the app installed, it should skip it, and continue to keep pinging computers and so on. I want the script to run until someone manually stops it, installed the app to all the pcs, or runs for 3 days.
This might be too much to ask, but... it would also be nice to see a report of all the computers where the app was successfully installed (I am sure this should be on the ForEACH portion).
Thank you all
function HarvesterMenu {
$PCName = read-host -Prompt "Enter the computer name"
$PCName = $PCName -replace '(^\s+|\s+$)','' -replace '\s+',' '
$pingPC = Test-Connection -ComputerName $PCName -Quiet -Count 1 -ErrorAction SilentlyContinue
if ($pingPC -eq $True) {
Write-host "ONLINE"
harvesterInstall($PCName)
}
else {
Write-host "OFFLINE"
}
}
function HarvesterInstall($InstallOnPC) {
$Sourcefile = "\\SERVER1\discovery03$\HarvesterRemote.msi"
Copy-Item -Path $Sourcefile -Destination "\\$InstallOnPC\c$\windows\temp\HarvesterRemote.msi"
Invoke-Command -ComputerName $InstallOnPC -ScriptBlock {
Start-Process c:\windows\temp\HarvesterRemote.msi -ArgumentList "/quiet" -Wait
}
Start-Sleep 3
$InstCompleted = Test-Path -Path '\\$InstallOnPC\C$\Program Files (x86)\Labs\Harvester Remote\Harvester\Harvester.exe'
Write-Host $InstCompleted
if ($InstCompleted -eq $True) {
Write-host "Harvester was successfuly installed on $InstallOnPC"
}
else {
Write-host "Something went wrong. Ping the server and try again"
}
$BacktoMenu = read-host -Prompt "Press Y to go back to the Main Menu or N to exist"
if ($backtoMenu -eq "y") {
harvesterMenu
}
else {
Exit
}
}
HarvesterMenu
You can do that easily with a Foreach-Loop.
U need a File with the pc's u wanna install the application. maybe .txt or .csv
Then u can just address it in a foreach.
something like that:
$InputFile = 'C:\Temp\lIST.csv'
$addresses = get-content $InputFile
foreach($address in $addresses) {
if (test-Connection -ComputerName $address -Count 1 -Quiet ) {
write-Host "$address Online"
#ur harvesterinstall
}
else
{
Write-Warning "$address Offline"
}
}
I am working on developing PowerShell script to automate a task on a remote server by using Invoke-Command with WinRM.
The script will take the server IP, test WinRM and "Get-Credential" cmdlet to establish session and use Invoke-Command to run another script on remote server. I have made significant progress of what I want to achieve, however, I am having trouble on how to setup the code so that when I press the "Cancel" or "X" button on Get-Credential prompt it should abort the script and return to the regular PowerShell command line prompt.
Below is what I have so far, I have erased the comments and description of the code to keep the number of words less in here.
function SS
{
Add-Type -AssemblyName System.Windows.Forms
$BInput = [System.Windows.Forms.MessageBox]::Show('Do you want to proceed?', 'Confirmation',[System.Windows.Forms.MessageBoxButtons]::YesNo)
switch ($BInput)
{
"Yes" {
while ($true)
{
$server=Read-Host "Enter Server IP Address"
set-item -Path WSMan:\localhost\Client\TrustedHosts -Value "$server" -Force
if(Test-WSMan -ComputerName $server -ErrorAction SilentlyContinue)
{
Write-Host "$server is accessible, enter credentials to connect"
while ($true)
{
$creden=Get-Credential -Message "Please enter the server credentials that you want to connect"
$serversession = New-Pssession -ComputerName $server -Credential $creden -ErrorAction SilentlyContinue
if(-not($serversession))
{
write-warning "Credentials are not valild, please try again"
}
else
{
write-host "$server is connected, starting the workflow ......"
Invoke-Command -Session $serversession -FilePath "C:\Temp\XXX.ps1"
}
}
Break
}
else
{
write-host "Windows Remote Management (WinRM) protocol is not running, please check service and confirm."
}
}
Get-Pssession | Remove-PSSession
}
"No" {
Break
}
}
}
I understand I have to apply the changes / logic after this line
$creden=Get-Credential -Message "Please enter the server credentials that you want to connect"
But can't seem to find it yet. I looked online and have taken different approaches but no success so far. I would like to have opinions or recommendations on how to tackle this, appreciate your help.
Thanks
What i'm seeing is that you may be thinking too much into it. A simple if statement should do the trick, try:
$creden=Get-Credential -Message "Please enter the server credentials that you want to connect"
if(!$creden){break}
Continuing from my comment.
Try this refactor of your use case.
Point of note: Note fully tested since I do not have an environment at this time to test.
Function Start-WorkFlow
{
<#
.Synopsis
Execute a workflow
.DESCRIPTION
Sets up a WinRM session to a remote host to execute the defined workflow
.EXAMPLE
Start-WorkFlow
.EXAMPLE
swf
.INPUTS
Remote host IPAddress
Remove host credentials
.OUTPUTS
Resutls of teh workflow
.NOTES
V 0.0.1 - Prototype script. Clean-Up before production use
.COMPONENT
Stand-alone script
.ROLE
Administrative actions
.FUNCTIONALITY
Implemetned error logic for each code block
Restrict the user input to only be a proper IPAddress
Validate TCPIP state
Validate WSman state
Establish a new session
Process workflow
Exit session
#>
[cmdletbinding(SupportsShouldProcess)]
[Alias('swf')]
Param
(
)
If ((Read-Host -Prompt 'Do you want to proceed: [Yes/No]') -eq 'No' )
{Break}
Else
{
Do {$RemoteServerIPAddress = (Read-Host -Prompt 'Enter Server IP Address')}
Until ($RemoteServerIPAddress -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
Get-ChildItem -Path 'WSMan:\localhost\Client\TrustedHosts'
Try
{
(Test-Connection -ComputerName $RemoteServerIPAddress -Count 1 -ErrorAction Stop).IPV4Address
# Set-Item -Path 'WSMan:\localhost\Client\TrustedHosts' -Value $RemoteServerIPAddress -Force
Get-ChildItem -Path 'WSMan:\localhost\Client\TrustedHosts'
Try
{
Test-WSMan -ComputerName $RemoteServerIPAddress -ErrorAction Stop
"$RemoteServerIPAddress is accessible, enter credentials to connect"
Do
{
$Creds = $null
$CredMesssage = 'Please enter the remote server credentials that you want to connect.'
$CredMesssage = "$CredMesssage If credentials are not valid, you will be prompted to re-enter them."
$Creds = Get-Credential -Message $CredMesssage
if(-Not $creds)
{
Write-Warning -Message 'Credential request cancelled.'
Start-Sleep -Seconds 3
Exit
}
$NewPSSessionSplat = #{
ComputerName = $RemoteServerIPAddress
Credential = $Creds
Name = 'RemoteSessionName'
ErrorAction = 'Stop'
}
New-PSSession $NewPSSessionSplat
}
Until (Get-PSSession -Name 'RemoteSessionName')
"$RemoteServerIPAddress is connected, starting the workflow ......"
Invoke-Command -Session $RemoteServerSession -FilePath 'C:\Temp\XXX.ps1'
}
Catch
{
Write-Warning -Message 'Session connection results:'
$PSitem.Exception.Message
}
Finally
{
Get-PSSession |
Remove-PSSession -ErrorAction SilentlyContinue
}
}
Catch
{
Write-Warning -Message "
The remote server $RemoteServerIPAddress is not available
Exiting the session."
Start-Sleep -Seconds 3
Exit
}
}
}
Start-WorkFlow
I am not quite sure how to explain my problem, but I have a function that installs Office, imagine the person that runs this script does not have internet connection or does not have enough space on her hard drive. I have the XML file set to hide the setup interface so the user can't see the installation process. Just to be clear all my code works fine, just want add this feature so that if something goes wrong while the user runs the script I know where the error was.
This is my function:
Function Install-Office365OfficeProducts{
Write-Host ""
Start-Sleep -Seconds 5
Write-Host "Installing Office 365 ProPlus..."
# Installing Office 365 ProPlus
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
This is what I have tried:
if (Install-Office365OfficeProducts -eq 0) {
Write-Host "FAILED"}
I am very confused, I thought that a function that runs with no error returns 1 and when it runs with errors returns 0.
Also have tried to put the code like this:
try {
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
} catch {
Write-Host "Failed!"
}
EDIT:
Basically i want to be shown an error if the Office setup is not finished...
#Thomas
Function Install-Office365Product{
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
try{
Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -NoNewWindow -ErrorAction Stop
}catch{
Write-Host "It was not possible to install the product!"
}
}
Your try/catch-block inside Install-Office365OfficeProducts is useless, because Install-Office365Product will not throw anything, except you pass wrong arguments. The try/catch-block inside Install-Office365Product will most likely also not catch anything. But you can of course evaluate the return code of your installer called with Start-Process:
function Install-Office365Product {
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
$process = Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Installation successful"
} else {
Write-Host "Installation failed"
}
}
Instead of writing to stdout, you can of course also throw an exception and handle it later in a higher function.
I have an issue with setting an ACL to a folder using PowerShell. It seems I was able to get through my code without any errors, but the folder still does not appear in the Security properties of the folder. The other articles I looked at seemed to have answers, but then comments, if any, that it didn't work, and after trying what the answers suggested, it did not result in the group appearing in the System properties of the folder.
My script so far is as follows:
$domain="DOMAIN"
$tldn="net"
$pathArr=#()
$pathArr+=$path1=Read-Host -Prompt "Enter first path"
$pathArr+=$path2=Read-Host -Prompt "Enter second path"
[int]$projectNumber=try { Read-Host -Prompt "Enter project number" } catch { Write-Host "Not a numeric value. Please try again."; exit }
[string]$mainFolder=[string]${projectNumber}+"_"+(Read-Host -Prompt "Please give the main folder name")
$projectNumberString=[string]$projectNumber
$projectName=Read-Host -Prompt "Please give the project name"
$fullProjectName="${projectNumberString}_${projectName}"
$pathArr+=$path3="$path1\$mainFolder"
$pathArr+=$path4="$path2\$mainFolder"
$pathArr+=$path5="$path3\$fullProjectName"
$pathArr+=$path6="$path4\$fullProjectName"
# Region: Create organizational units in Active Directory
# Names
$ouN1="XYZOU"
$ouN2="ABCOU"
# Paths
$ouP0="DC=$domain,DC=$tldn"
$ouP1="OU=$ouN1,$ouP0"
$ouP2="OU=$ouN2,$ouP1"
Write-Host "Checking for required origanization units..."
try
{
New-ADOrganizationalUnit -Name $ouN1 -Path $ouP1
New-ADOrganizationalUnit -Name $ouN2 -Path $ouP2
}
catch
{
Out-Null
}
EDIT
As per Mickey's comment, I added this code to test the path of $path6'
if ( Test-Path -Path "$path6" )
{
Write-Host "$path6"
Write-Host "Path exists."
}
else
{
Write-Host "Path does not exist."
}
The result was that the path wrote to the host and said Path exists..
Write-Host "Creating AD Group..."
[string]$group="BEST_${projectNumberString}"
$groupdomain="$domain\$group"
$ADGroupParams= #{
'Name' = "$group"
'SamAccountName' = "$group"
'GroupCategory' = "Security"
'GroupScope' = "Global"
'DisplayName' = "$group"
'Path' = "OU=MyBusinessOU,DC=$domain,DC=$tldn"
'Description' = "Test share"
}
$secgroup=New-ADGroup #ADGroupParams
# Region: Set permissions
Write-Host "Setting permissions..."
# get permissions
$acl = Get-Acl -Path $path6
# add a new permission
$InheritanceFlags=[System.Security.AccessControl.InheritanceFlags]”ContainerInherit, ObjectInherit”
$FileSystemAccessRights=[System.Security.AccessControl.FileSystemRights]"Traverse","Executefile","ListDirectory","ReadData", "ReadAttributes", "ReadExtendedAttributes","CreateFiles","WriteData", 'ContainerInherit, ObjectInherit', "CreateDirectories","AppendData", "WriteAttributes", "WriteExtendedAttributes", "DeleteSubdirectoriesAndFiles", "ReadPermissions"
$InheritanceFlags=[System.Security.AccessControl.InheritanceFlags]”ContainerInherit, ObjectInherit”
$PropagationFlags=[System.Security.AccessControl.PropagationFlags]”None”
$AccessControl=[System.Security.AccessControl.AccessControlType]”Allow”
$permission = "$groupdomain", "$InheritanceFlags", "$PropagationFlags", "$AccessControl"
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)
# set new permissions
$acl | Set-Acl -Path $path6
I tried Set-Acl -ACLObject:$acl -Path:$path6 and that didn't work either.
Again, I am not getting any errors at all.
I am running PowerShell 4.0 in PowerShell ISE on Windows Server 2012 R2. I am logged in as Administrator.
If you have any ideas, I am open to them. To be clear, my goal is to add the $groupdomain to a folder called path6, and have the ACLs outlined here applied to that group.
Thank you for your help in advance.
I wrote a script which will
stop different services of two or more servers (one server containing two services),
take a back up of respected folders (folder name starting with Job) to backup folder,
copy newest files from staging location to destination folder,
start different services in servers.
Looks like I have chosen long procedure to do it. Please suggest if I can modify the script.
Function Get-Kettle
{
$DestinationFolder = "D:\AppCode\Kettle"
$BackUpFolder = "D:\AppCode\Kettle\BackUp"
$StagingFolder = "\\server001\e$\Packages\RTO\RTO\ETL"
$ServerList = #("server222")
$ServicesList = #("WindowsScheduler","WindowsSchedulerLogon")
Foreach ($Server in $ServerList)
{
$CheckStagingFolder = Get-ChildItem $StagingFolder
if($CheckStagingFolder.count)
{
Write-Host "StagingFolder contains files.. Continue with Deployment"
if((Test-Path "$DestinationFolder") -and (Test-Path "$BackUpFolder"))
{
Write-Host "Taking BackUp"
Copy-Item "$DestinationFolder" -Destination "$BackUpFolder"
Write-Host "BackUp is completed"
Write-Host "Stopping the service WindowsSchedulerLogon"
Stop-Service $Server.WindowsSchedulerLogon -Force
Write-Host "WindowsSchedulerLogon service is stopped"
Write-Host "Stopping the service WindowsScheduler"
Stop-Service WindowsScheduler -Force
Write-Host "WindowsScheduler service is stopped"
Copy-Item "$StagingFolder" -Destination "$DestinationFolder" -Recurse
Write-Host "Starting the service WindowsSchedulerLogon"
Start-Service WindowsSchedulerLogon -Force
Write-Host "WindowsSchedulerLogon service is Started"
Write-Host "Starting the service WindowsScheduler"
Start-Service WindowsScheduler -Force
Write-Host "WindowsScheduler service is started"
Write-Host "Deployment is completed"
}
else
{
Write-Host "No Destination and BackUp Folder..Script Exiting...!"
Exit;
}
}
else
{
Write-Host "StagingFolder does not contains files.. Exiting with Deployment"
exit;
}
}
}
Get-Kettle
I agree this would be better placed on Code Review but as it's still open here I thought I'd suggest some improvements:
Add [cmdletbinding()] and put your variables at the top in a Param() block. Cmdletbinding means you get access to a set of common parameters that allow you to utilise a series of builtin functionality (such as Write-Verbose which I will mention later). Putting your variables in a Param block means you can change them by calling them in your Function, just like you do with built-in cmdlets.
Add Begin, Process and End blocks. I can't see any need to do anything in Begin or End at this point, but the rest of your code can live in Process. This means you could later change this function to support pipeline input if you wanted to.
Change all your Write-Host statements to Write-Verbose. Write-Host is considered harmful because it interferes with automation. Write-Verbose means you can see information messages when you want to but not by default, and those messages to a separate output stream that doesn't then interfere with the output of the function. To see the messages simply do Get-Kettle -Verbose. A further benefit of this is that it will also turn on the -Verbose messages that are built in to the standard cmdlets you are using (such as Copy-Item or Start-Service).
You can should change your last two Write-Host statements (in the else blocks) to Write-Warning messages, these will then always be displayed (regardless of -verbose etc.) when you need to inform the user of something but again, do not interfere the default output pipeline.
You can drop the Exit statements as they not really achieving anything.
Other improvements you might consider include:
Accepting input from the pipeline.
Parameter sets or validating on your parameters and setting variable types on them (such as [string] etc.).
Supporting -confirm and -whatif by adding supportsshouldprocess and putting these around the parts of the script that make changes.
All of these things are features of what is called an Advanced function or Cmdlet. If you haven't read it, take a look at Learn PowerShell Toolmaking in a Month of Lunches as it covers these topics and a lot more.
Here's a copy of your code with the first set of improvements I suggested implemented:
Function Get-Kettle
{
[cmdletbinding()]
Param(
$DestinationFolder = "D:\AppCode\Kettle",
$BackUpFolder = "D:\AppCode\Kettle\BackUp",
$StagingFolder = "\\server001\e$\Packages\RTO\RTO\ETL",
$ServerList = #("server222"),
$ServicesList = #("WindowsScheduler","WindowsSchedulerLogon")
)
Begin {}
Process {
Foreach ($Server in $ServerList)
{
$CheckStagingFolder = Get-ChildItem $StagingFolder
if($CheckStagingFolder.count)
{
Write-Verbose "StagingFolder contains files.. Continue with Deployment"
if((Test-Path "$DestinationFolder") -and (Test-Path "$BackUpFolder"))
{
Write-Verbose "Taking BackUp"
Copy-Item "$DestinationFolder" -Destination "$BackUpFolder"
Write-Verbose "BackUp is completed"
Write-Verbose "Stopping the service WindowsSchedulerLogon"
Stop-Service $Server.WindowsSchedulerLogon -Force
Write-Verbose "WindowsSchedulerLogon service is stopped"
Write-Verbose "Stopping the service WindowsScheduler"
Stop-Service WindowsScheduler -Force
Write-Verbose "WindowsScheduler service is stopped"
Copy-Item "$StagingFolder" -Destination "$DestinationFolder" -Recurse
Write-Verbose "Starting the service WindowsSchedulerLogon"
Start-Service WindowsSchedulerLogon -Force
Write-Verbose "WindowsSchedulerLogon service is Started"
Write-Verbose "Starting the service WindowsScheduler"
Start-Service WindowsScheduler -Force
Write-Verbose "WindowsScheduler service is started"
Write-Verbose "Deployment is completed"
}
else
{
Write-Warning "No Destination and BackUp Folder..Script Exiting...!"
}
}
else
{
Write-Warning "StagingFolder does not contains files.. Exiting with Deployment"
}
}
}
End {}
}
Get-Kettle -Verbose