Powershell run script with Import-Module - powershell

I have created a module for my admin group with some functions that automate some procedures we commonly perform (add administrators to remote machines, C drive cleanup, etc...)
One of the prerequisites for these functions is the generation of a series of 7 credentials, one for each domain we work in.
Is there a way to get a scriptblock to run when you import a module, or is this something I should add to each persons profile?
A commenter mentioned I could just add it to the module.psm1 file, but that didn't work. Here is the code I am trying to run.
$creds = Import-Csv [csvfile]
$key = Get-Content [keyfile]
foreach ($cred in $creds) {
$user = $cred.User
$password = $cred.Hash | ConvertTo-SecureString -Key $key
$i = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user,$password
New-Variable -Name ($cred.Domain + "_Cred") -Value $i -Force
}
Running this manually works fine, but it isn't creating the credentials when run from the Import-Module command.

Any code that's not a function will run when you import the module.

A handy tip when working with modules: & and . have what may be undocumented functionality. With either you can give two arguments, the first is a module reference (from get-module or similar) and second is a script. With the module reference parameter the script will run in the context of the module. So for example:
& $myMod {$usa_cred}
will output the value of $use_cred even if it hasn't been exported. This is useful for debugging scripts. Also modules can have embedded modules and & $myMod {gmo} will list those sub modules. By nesting & or . you can access sub-modules context.

Related

Reference WinSCP.exe from PowerShell script executed from SSIS

I am trying to execute a PowerShell script from within SSIS. My script starts with the Add-Type -Path "WinSCPnet.dll" and it is erroring out because it cannot find the WinSCP.exe in the folder that houses my PowerShell script. Come to find out the server admin did NOT install WinSCP into the GAC. Is this creating my problem?
If so, how and where can I reference the WinSCP.exe in my script using $session.ExecutablePath? Any help/direction would be appreciated. Thanks.
Here is my script below:
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Declare variables
$date = Get-Date
$dateStr = $date.ToString("yyyyMMdd")
#$fileDirectory = "\\abicfs2\apps\CoverageVerifier\"
#$filePath = "\\abicfs2\apps\CoverageVerifier\cvgver." + $dateStr + ".0101"
$filePath = "\\empqaapp1\coverageverifier_scripts\CoverageVerifier\cvgver.20190121.0101"
# Write-Output $filePath
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "secureftp.iso.com"
UserName = "account"
Password = "password"
SshHostKeyFingerprint = "ssh-rsa 2048 8C1lwAjxCNRF6B4kbPIeW52/GB+98FmLMt0AJNf/Sf4="
}
#$sessionOptions.AddRawSettings("FSProtocol", "2")
$session = New-Object WinSCP.Session
# $session.SessionLogPath = "\\share\apps\CoverageVerifier\UploadLog.log"
try
{
# Connect
$session.Open($sessionOptions)
# Transfer files
$session.PutFiles($filePath,"/").Check()
}
finally
{
$session.Dispose()
}
I am trying to execute a Powershell script from within SSIS
It seems you believe you need to have WinSCP .NET assembly in GAC, so that you can execute it from a PowerShell script executed from SSIS. I do not think it's true. You need an assembly in GAC, only if you directly use it from an SSIS code. What is not your case.
You can simply store the WinSCPnet.dll and WinSCP.exe to your PowerShell script directory.
Anyway to answer your question:
If so, how and where can I reference the WinSCP.exe in my script using $session.ExecutablePath?
$session = New-Object WinSCP.Session
$session.ExecutablePath = "C:\path\WinSCP.exe"
(but as per above, I do not think you need it)
Come to find out the server admin did NOT install WinSCP into the GAC.
You cannot install .exe file to GAC.
Excerpt from WinSCP assembly installation instruction (https://winscp.net/eng/docs/library_install):
The package includes the assembly itself (winscpnet.dll) and a
required dependency, WinSCP executable winscp.exe.
The binaries interact with each other and must be kept in the same
folder for the assembly to work. In rare situations this is not
possible (e.g. when installing the assembly to GAC), make use of the
Session.ExecutablePath property to force the assembly to look for the
winscp.exe in a different location.

Remove Class from Memory in PowerShell

I've created a class called "Application" and loaded it in my main script with:
Import-Module -NAME "C:\PowerShell_Scripts\Class\Application.ps1" -GLOBAL -FORCE;
However if I ONLY make changes to the class file and run the code in PowerShell ISE none of the changes are applied. It's almost as if the class is still in memory even though I've used -FORCE.
I've also tried to remove the module before loading it and the same issue happens:
Remove-Module "Application" -ErrorAction Ignore -FORCE;
Import-Module -NAME "C:\PowerShell_Scripts\Class\Application.ps1" -GLOBAL -FORCE;
If I make a single character change in my main script then it reloads the class! But I shouldn't have to modify the main script to force PowerShell to reload the class, that just seems silly.
Is there a way to remove the Application class from memory if it exists?
NOTE: Files with just functions in them work file. This only applies to Class imports.
Addition: In the console, if I run the Remove-Module command it runs successfully but I can STILL create new objects with:
$appDetails = [Application]::new($applicationID);
Doesn't make sense to me...
MAIN SCRIPT:
# Application Details
# -----------------
#ID
$applicationID = 1;
############################################
#
# Load Supporting Scripts
#
############################################
try
{
Remove-Module "Application" -ErrorAction Ignore -FORCE;
Remove-Module "Common" -ErrorAction Ignore -FORCE;
Remove-Module "ServerData" -ErrorAction Ignore -FORCE;
Import-Module -NAME "C:\PowerShell_Scripts\Common.ps1" -GLOBAL -FORCE;
Import-Module -NAME "C:\PowerShell_Scripts\ServerData.ps1" -GLOBAL -FORCE;
Import-Module -NAME "C:\PowerShell_Scripts\Class\Application.ps1" -GLOBAL -FORCE;
}
catch
{
Write-Host "`nError: Cannot load required PowerShell scripts. Ensure C:\PowerShell_Scripts\ exists and has the required files." -ForegroundColor Red;
EXIT;
}
############################################
#
# Load the SharePoint Snapin Module.
#
############################################
LoadSharePointModule;
############################################
#
# Display component details to user.
#
############################################
#Create object of "Application" to get app details based on the ID.
$appDetails = [Application]::new($applicationID);
Write-Host "Ending ......";
APPLICATION CLASS FILE
Class Application
{
#Class Properties
[STRING] $appName;
[INT32] $appID;
[INT32] $versionMajor;
[INT32] $versionOS;
[INT32] $versionCentraAdmin;
[INT32] $versionMain;
[INT32] $versionGUI;
[INT32] $versionWorkflow;
[INT32] $versionForm;
[INT32] $versionVS;
[INT32] $versionOther;
[INT32] $versionFull;
[OBJECT] $spDevSite;
[OBJECT] $versionList;
#Constructor: Setup class properties.
Application ([INT32] $appID)
{
Write-Host "`nGathering application details ..." -ForegroundColor Yellow;
try
{
#Get the SharePoint Developer site Object.
$this.spDevSite = Get-SPWeb -ErrorAction Stop $GLOBAL:spDevURL;
}
catch
{
Write-Host "`nUnable to connect to SharePoint Developer site!: $($GLOBAL:spDevURL)";
#EXIT;
}
#Assign class property.
$this.appID = $appID;
}
}
I have deliberately set the URL for $GLOBAL:spDevURL; so that the Constructor fails for this test. It fails normally and displays
Write-Host "`nUnable to connect to SharePoint Developer site!: $($GLOBAL:spDevURL)";
But if I make a change to this line and run the script, the change is not applied.
The Known Issue
There is a known issue in PowerShell 5.0 and 5.1 that explains this behavior. The issue was acknowledged by DongBo Wang on the PowerShell 6 team in November 2016. He wrote the following:
"The module analysis result is stored in a cache with the module file path as the key and the PSModuleInfo object as the value. The cache entries are not properly invalidated based on the LastWriteTime of the module file, and thus same cached value got reused."
In other words, PowerShell 5.0, 5.1, and 6.0 keeps (and uses) old copies of classes in memory when it shouldn't.
Implications
This issue causes considerable problems for development using PowerShell classes if you do not compensate for it. I wrote a test that covers about 100 of the scenarios where class reloading is important. Vaguely speaking, in about 17 of those scenarios PowerShell 5.0 and 5.1 doesn't reload the class when it should. This means using the same session across edits creates a real likelihood the interpreter will have cached duplicate copies of the same or similar classes. That makes behavior unpredictable and causes strange results that cannot be troubleshot.
Workaround
I have found that you can still be productive developing using PowerShell classes. You just need to perform each test run in a fresh PowerShell session when a project involves PowerShell classes whose source the PowerShell interpreter may consider to have changed. The customary way to do this is to invoke your test command from your PowerShell console by invoking powershell.exe:
powershell.exe -Command { Invoke-Pester }
That's not a terribly inefficient test-edit-test cycle if you've got tight unit tests. If you need to step through code, you'll need to launch a fresh copy of ISE each time you make an edit.
With this workaround, I have found the productivity impact of this bug to be manageable. I developed this and this entirely using this workaround. Each of those projects involve a significant amount of code involving PowerShell classes.

Discovery Script is not working

Below is my script with identifiers scrubbed. The Base Class for Diagnostics is MyCompany.MyApp and the base class of that is Windows Computer. If I run this on the target machine directly with parameters I am getting XML returned. If I run from my PC it is blank. That makes sense locally, but I thought when you run discoveries that the agent runs it on the machine you are targeting? All my other discoveries thus far have been registry so it is possible I am doing something completely wrong.
Param($sourceId, $managedEntityId, $ComputerName)
$api = New-Object -ComObject 'MOM.ScriptAPI'
$discoveryData = $api.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)
$Diagnostics = Invoke-Command -ComputerName $ComputerName {
Get-WebApplication -Name "diagnostics"
}
foreach ($x in $Diagnostics)
{
$instance = $discoveryData.CreateClassInstance("$MPElement[Name='MyCompany.MyApp.Diagnostics']$")
$instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $ComputerName)
$discoveryData.AddInstance($instance)
}
As the first, most obvious reason, you don't output the discovery data onto output stream (StdOut). Just simple add $discoveryData at a new line by the end of your script.
Moreover, more information about your class and MP architecture required. I'd probably recommend you to use Windows!Microsoft.Windows.ComputerRole as base class and host it on target computer object.
Regards
Max

Functions from an imported module are not available in a powershell script

I am working with PowerShell V 4.0 on a Windows Server machine. I have encountered a problem that I am not able to debug or find a solution.
I have a ps1 script that imports two psm1 modules A and B. (B in turn imports another module C).
Import-Module $PSScriptRoot\..\lib\infra\moduleA.psm1
Import-Module $PSScriptRoot\..\lib\nwm\moduleB.psm1
#get-logger function works fine. This is defined in moduleA
$log = get-logger
$smisXmlData = [xml] $smisConfigData
#The function get-hostLocalCred is defined in moduleB. This is where the error occurs.
($username, $password) = get-hostLocalCred $smisXmlData
I am not able to use the functions from the second module moduleB in the script. When I run the script, it throws errors where ever a function from the module B is used. The error is below (get-hostLocalCred is the function name.)
get-hostLocalCred : The term 'get-hostLocalCred' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
The following is the content in the moduleB.
#Importing moduleC.
Import-Module $PSScriptRoot/../infra/moduleC.psm1
#Defining required functions. These are the functions that are not available in the above script.
function get-hostLocalCred {
Param($xmldata)
$log.entry()
$username = $xmldata.component.yourhost.localcred.username
$log.debug("USERNAME: $username")
$password = $xmldata.component.yourhost.localcred.password
$log.exit()
return $username, $password
}
function new-ArrayCredObj {
Param([Parameter(Mandatory = $true)] $user,
[Parameter(Mandatory = $true)] $password)
$log.entry()
$log.info("Creating custom Object")
$log.debug("User : $user")
$log.debug("Pwd : $password")
$arrayCred = New-Object psobject
$arrayCred | Add-Member -MemberType NoteProperty -Name auser -Value $user
$arrayCred | Add-Member -MemberType NoteProperty -Name password -Value $password
$log.exit()
return $arrayCred
}
.
.
.
.
The functions from moduleA are being executed properly, but I am not able to execute the function from moduleB.
Also, After running the script in the console, When I try to lookup the functions available in the Module B, using the following commandlet,
Get-Command -Module ModuleB
I only see the functions defined in the ModuleC, which is imported by moduleB, but I donot see any of the functions defined in the moduleB. I have been working with powershell but this is the first time I have seen this issue.
When I do a Get-Module, I see only moduleA and moduleB.
All the Modules are imported in the following way:
Import-Module $PSScriptRoot/../lib/newmodules/moduleB.psm1
Importing modules Globally has also not solved the problem.
Importing modules by giving actual path like following has also not solved the issue.
Import-Module C:\folderpath\ModuleB.psm1
All the functions in all the modules have been defined as following. There is no difference in the function definition in any of the modules.
function get-hostLocalCred {
Param($xmldata)
# Function Definition
return $result
}
I might be missing a simple thing but I am not able to get it. I have been importing modules normally and working with them since long time but this is the first time I ran into this issue. Thanks in advance for the help.
Maybe you are updating the code in moduleB while testing it from a PS session.
Then according to Microsoft's documentation you should use the Force parameter of Import-Module if your module has changed during the same calling session.
Import-Module $PSScriptRoot\..\lib\nwm\moduleB.psm1 -Force
This problem occurs when your manifest (.psd1) does not have a root module specified.
#{
# Script module or binary module file associated with this manifest.
RootModule = 'mymodule.psm1'
...
Previously, it would have been commented out by default when generating it from New-ModuleManifest
#{
# Script module or binary module file associated with this manifest.
# RootModule = ''
...
I had similar issue and it worked by adding "-Scope Global" to import-module cmdlet

Powershell - Copying File to Remote Host and Executing Install exe using WMI

EDITED: Here is my code now. The install file does copy to the remote host. However, the WMI portion does not install the .exe file, and no errors are returned. Perhaps this is a syntax error with WMI? Is there a way to just run the installer silently with PsExec? Thanks again for all the help sorry for the confusion:
#declare params
param (
[string]$finalCountdownPath = "",
[string]$slashes = "\\",
[string]$pathOnRemoteHost = "c:\temp\",
[string]$targetJavaComputer = "",
[string]$compname = "",
[string]$tempPathTarget = "\C$\temp\"
)
# user enters target host/computer
$targetJavaComputer = Read-Host "Enter the name of the computer on which you wish to install Java:"
[string]$compname = $slashes + $targetJavaComputer
[string]$finalCountdownPath = $compname + $tempPathTarget
#[string]$tempPathTarget2 =
#[string]$finalCountdownPath2 = $compname + $
# say copy install media to remote host
echo "Copying install file and running installer silently please wait..."
# create temp dir if does not exist, if exist copy install media
# if does not exist create dir, copy dummy file, copy install media
# either case will execute install of .exe via WMII
#[string]$finalCountdownPath = $compname + $tempPathTarget;
if ((Test-Path -Path $finalCountdownPath) )
{
copy c:\hdatools\java\jre-7u60-windows-i586.exe $finalCountdownPath
([WMICLASS]"\\$targetJavaComputer\ROOT\CIMV2:win32_process").Create("cmd.exe /c c:\temp\java\jre-7u60-windows-i586.exe /s /v`" /qn")
}
else {
New-Item -Path $finalCountdownPath -type directory -Force
copy c:\hdatools\dummy.txt $finalCountdownPath
copy "c:\hdatools\java\jre-7u60-windows-i586.exe" $finalCountdownPath
([WMICLASS]"\\$targetJavaComputer\ROOT\CIMV2:win32_process").Create("cmd.exe /c c:\temp\java\jre-7u60-windows-i586.exe /s /v`" /qn")
}
I was trying to get $Job = Invoke-Command -Session $Session -Scriptblock $Script to allow me to copy files on a different server, because I needed to off load it from the server it was running from. I was using the PowerShell Copy-Item to do it. But the running PowerShell script waits until the file is done copying to return.
I want it to take as little resources as possible on the server that the powershell is running to spawn off the process on another server to copy the file. I tried to user various other schemes out there, but they didn't work or the way I needed them to work. (Seemed kind of kludgey or too complex to me.) Maybe some of them could have worked? But I found a solution that I like that works best for me, which is pretty easy. (Except for some of the back end configuration that may be needed if it is is not already setup.)
Background:
I am running a SQLServer Job which invokes Powershell to run a script which backups databases, copies backup files, and deletes older backup files, with parameters passed into it. Our server is configured to allow PowerShell to run and under the pre-setup User account with SQL Server Admin and dbo privileges in an Active Directory account to allow it to see various places on our Network as well.
But we don't want it to take the resources away from the main server. The PowerShell script that was to be run would backup the database Log file and then use the another server to asynchronously copy the file itself and not make the SQL Server Job/PowerShell wait for it. We wanted it to happen right after the backup.
Here is my new way, using WMI, using Windows Integrate Security:
$ComputerName = "kithhelpdesk"
([Wmiclass]'Win32_Process').GetMethodParameters('Create')
Invoke-WmiMethod -ComputerName RemoteServerToRunOn -Path win32_process -Name create -ArgumentList 'powershell.exe -Command "Copy-Item -Path \\YourShareSource\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak -Destination \\YourShareDestination\YourDatabase_2018-08-07_11-45.log.bak"'
Here is my new way using passed in Credentials, and building arg list variable:
$Username = "YouDomain\YourDomainUser"
$Password = "P#ssw0rd27"
$ComputerName = "RemoteServerToRunOn"
$FromFile = "\\YourShareSource\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak"
$ToFile = "\\YourShareDestination\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak"
$ArgumentList = 'powershell.exe -Command "Copy-Item -Path ' + $FromFile + ' -Destination ' + $ToFile + '"'
$SecurePassWord = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $Username, $SecurePassWord
([Wmiclass]'Win32_Process').GetMethodParameters('Create')
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList $ArgumentList -Credential $Cred
We think that this above one is the preferred one to use.
You can also run a specific powershell that will do what you want it to do (even passing in parameters to it):
Invoke-WmiMethod -ComputerName RemoteServerToRunOn -Path win32_process -Name create -ArgumentList 'powershell.exe -file "C:\PS\Test1.ps1"'
This example could be changed to pass in parameters to the Test1.ps1 PowerShell script to make it more flexible and reusable. And you may also want to pass in a Credential like we used in a previous example above.
Help configuring WMI:
I got the main gist of this working from: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/invoke-wmimethod?view=powershell-5.1
But it may have also needed WMI configuration using:
https://helpcenter.gsx.com/hc/en-us/articles/202447926-How-to-Configure-Windows-Remote-PowerShell-Access-for-Non-Privileged-User-Accounts?flash_digest=bec1f6a29327161f08e1f2db77e64856b433cb5a
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enable-psremoting?view=powershell-5.1
Powershell New-PSSession Access Denied - Administrator Account
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/invoke-wmimethod?view=powershell-5.1 (I used to get how to call Invoke-WmiMethod).
https://learn.microsoft.com/en-us/powershell/scripting/core-powershell/console/powershell.exe-command-line-help?view=powershell-6 (I used to get syntax of command line)
I didn't use this one, but could have: How to execute a command in a remote computer?
I don't know for sure if all of the steps in the web articles above are needed, I suspect not. But I thought I was going to be using the Invoke-Command PowerShell statement to copy the files on a remote server, but left my changes from the articles above that I did intact mostly I believe.
You will need a dedicated User setup in Active Directory, and to configure the user accounts that SQL Server and SQL Server Agent are running under to give the main calling PowerShell the privileges needed to access the network and other things to, and can be used to run the PowerShell on the remote server as well. And you may need to configure SQLServer to allow SQL Server Jobs or Stored Procedures to be able to call PowerShell scripts like I did. But this is outside the scope of this post. You Google other places on the internet to show you how to do that.