How Do I Configure Changable Connection Strings in a Powershell Binary Module? - c#

I have a PowerShell module I wrote in C# which using Entity Framework to access a MS SQL server. In my project I have created a module.psd1 file. This file specifies a script (LoadAppConfig.ps1) to run before loading the dll. The script reads app.config and adds the connection string to the current app domain.
Is there a better way to do this? I know some modules can take parameters when you load them, but I haven't been able to find a way to implement this in C#. Or, is there a way to embed a default value in the module, which can be changed by a cmdlet? I would like to avoid having to recompile the module to change data sources if possible.
LoadAppConfig.ps1
Add-Type -AssemblyName System.Configuration
$path = $PSScriptRoot
$configPath = Join-Path -Path $path -ChildPath "App.Config"
$connectionstrings = [System.Configuration.ConfigurationManager]::ConnectionStrings
$collection = [System.Configuration.ConfigurationElementCollection]
$field = $collection.GetField(
"bReadOnly",
[System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
$field.SetValue($connectionstrings, $false)
[xml]$xml = gc $configPath
$name = $xml.configuration.connectionStrings.add.name
$newconnectionstring = $xml.configuration.connectionStrings.add.connectionString
$provider = "System.Data.EntityClient"
if ($connectionstrings.name -notcontains $name)
{
$Entities = [System.Configuration.ConnectionStringSettings]::new($name, $newconnectionstring, $provider)
$connectionstrings.add($Entities)
}

Related

Updating PATH environment variable via registry setting using C# causes path to no longer be valid

I have a folder that I want to add to the PATH variable under Environment Variables (for the machine). I am appending the folder to the path via the registry setting. SYSTEM\CurrentControlSet\Control\Session Manager\Environment.
Here is a snippet of the code where I read the registry setting. And I perform a registry update on the setting, so nothing revolutionary.
String keyName = #"SYSTEM\CurrentControlSet\Control\Session Manager\Environment\";
string existingPathFolderVariable = (string)Registry.LocalMachine.OpenSubKey(keyName).GetValue("PATH", "", RegistryValueOptions.DoNotExpandEnvironmentNames);
string keyValue = #"c:\MyPath\";
if ( !existingPathFolderVariable.Contains(keyValue) )
{
if (!existingPathFolderVariable.EndsWith(";", StringComparison.InvariantCulture))
{
existingPathFolderVariable += ';';
}
Followed by code to update registry value, standard registry functions.
}
I tried various options of updating the registry including using powershell.
$oldpath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).path
$newpath = "$oldpath;c:\install\sysinternals"
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newPath
(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
Though the path is updated and the values look correct, the path is no longer valid. The commands under the System32 folder are no longer valid. If I perform a ping, I get the unknown command message. Same for ipconfig and other commands.
I read that I could use the SetEnvironmentVariable function. But I do not want the values expanded.
If I copy the line, delete the line, and add the line via the registry setting or UI, the problem is resolved.
Any suggestions on how to resolve the problem?

C# - Python interop using Python.Net [No module named '_ctypes']

I am trying to use Python.NET to perform interop between C# and Python on a Windows machine.
Specifically I have the following code.
PythonEngine.PythonHome = #"C:\Python\3_5_4";
PythonEngine.PythonPath = #"C:\Python\3_5_4\Lib;C:\Python\3_5_4\Lib\site-packages";
using (Python.Runtime.Py.GIL())
{
dynamic np = Py.Import("numpy");
dynamic sin = np.sin;
Console.WriteLine(sin);
}
While I can successfully execute a general python statement as such:
var res = PythonEngine.Eval("1 + 1");
Console.WriteLine(res);
//res = 2
Which indicates that the python engine itself is working successfully, I can also invoke something like this:
var html = Py.Import("html");
Console.WriteLine(html);
//html = <module 'html' from 'C:\\Python\\3_5_4\\Lib\\html\\__init__.py'>
Which further indicates that the module loading functionality is also working correctly.
However whenever I attempt to invoke the line:
dynamic np = Py.Import("numpy");
I receive the following error:
{"ImportError : No module named '_ctypes'"}.
[' File "C:\\Python\\3_5_4\\Lib\\site-packages\\numpy\\__init__.py", line 140, in <module>\n from . import _distributor_init\n', ' File "C:\\Python\\3_5_4\\Lib\\site-packages\\numpy\\_distributor_init.py", line 9, in <module>\n from ctypes import WinDLL\n', ' File "C:\\Python\\3_5_4\\Lib\\ctypes\\__init__.py", line 8, in <module>\n from _ctypes import Union, Structure, Array\n']
The file referenced is located # 'C:\Python\3_5_4\Lib\ctypes\__init__.py'.
I have verified that all the expected paths are set correctly and that the Ctypes folder exists in my python path.
I have tried everything from uninstalling and reinstalling Python to modifying the ctypes/__init__.py file to try and import Ctypes directly to no effect.
Having researched this topic online throughly I have found a number of suggestions which point to running yum install libffi-devel as detailed here. However this does not appear to be something that I can perform on windows given that yum appears to be a Linux only application.
Can anyone provide any guidance?

How to update url stored in Sharepoint add-in app file?

I created an High-Trust add-in for SharePoint 2013 with custom ribbon action and custom menu action.
For this, I have an ASP.NET MVC WebSite with the methods in the controller which match with the virtual urls put as custom action url. So, in the different elements.xml files, I filled action urls using the token 'remoteUrl', so no problem with the mapping.
When i create a package with VS2013, I write the url of my website which is on VM reachable from SP Server, and the client ID (I got from SP while registring my app). When I click on 'Finish', VS2013 generates a file '.app' which can be imported in SP online store or SP internal store.
Here is my problem, if I need to change the address of my website (which is stored in the app file, VS2013 just replaces the token 'RemoteUrl' with the url I give to it), is there any clean way to update the app file or may be if possible, directly the app stored in the SP application store (local to the server) ?
I found nothing for this problem. I saw few things about updating app with events and web services, but I didn't understood.
[EDIT] : I didn't understood that I have to change app version each time I need to update it that's why It didn't worked. Also, it seems that there is no other way to update the url in app file than modifying the AppManifest.xml in app file (which is a zip).
In one of my projects we used to do it with the following PowerShell script. It extracted the app file (it's just a ZIP) and modified multiple nodes in the manifest XML.
For packaging it uses a local copy of 7zip.
function ModifyAppPackage($appPackagePath, $applicationUrl, $clientId){
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem");
$item = get-item $appPackagePath;
$zipFilePath = Join-Path $item.Directory.FullName $($item.BaseName + ".zip");
Copy-Item $item $zipFilePath;
$unzipDirectory = Join-Path $PSScriptRoot "\Temp";
New-Item -ItemType Directory -Force -Path $unzipDirectory;
if (Test-Path -Path $unzipDirectory\*)
{
Remove-Item $unzipDirectory\* -Force -Confirm:$false -Recurse:$true;
}
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipFilePath, $unzipDirectory);
$modifiedFile = Join-Path $unzipDirectory "modified.txt"
if (Test-Path -Path $modifiedFile)
{
$modifiedContent = Get-Content $modifiedFile
if ($modifiedContent -eq $applicationUrl)
{
Remove-Item $unzipDirectory -Confirm:$false -Recurse:$true;
Remove-Item $zipFilePath;
return;
}
Remove-Item $modifiedFile;
}
$modifiedFileContent = $applicationUrl;
$modifiedFileContent >> $modifiedFile;
$manifestFileName = "AppManifest.xml";
$manifestFilePath = Join-Path $unzipDirectory $manifestFileName;
$manifestXml = [xml](get-content $manifestFilePath);
$nameSpaceManager = New-Object System.Xml.XmlNamespaceManager($manifestXml.NameTable);
$nameSpaceManager.AddNamespace("ns", $manifestXml.DocumentElement.NamespaceURI);
$startPageElement = $manifestXml.SelectSingleNode("/ns:App/ns:Properties/ns:StartPage", $nameSpaceManager);
$StartPage = $applicationUrl + "?{StandardTokens}"
$startPageElement.'#text' = $StartPage
$InstalledEventEndpointElement = $manifestXml.SelectSingleNode("/ns:App/ns:Properties/ns:InstalledEventEndpoint", $nameSpaceManager);
$InstalledEventEndpoint = $applicationUrl + "/Services/AppEventReceiver.svc"
$InstalledEventEndpointElement.'#text' = $InstalledEventEndpoint
$clientIdElement = $manifestXml.SelectSingleNode("/ns:App/ns:AppPrincipal/ns:RemoteWebApplication", $nameSpaceManager);
$clientIdElement.ClientId = $clientId;
$manifestXml.Save($manifestFilePath);
if (Test-Path -Path $zipFilePath)
{
Remove-Item $zipFilePath;
}
$pathToZipExe = $("$PSScriptRoot\7za.exe");
[Array]$arguments = "a", "-tzip", "$zipFilePath", "$unzipDirectory\*.*", "-r";
& $pathToZipExe $arguments;
# Cleanup
Remove-Item $unzipDirectory -Confirm:$false -Recurse:$true;
Remove-Item $appPackagePath -Confirm:$false;
# Rename new zip to .app
Rename-Item $zipFilePath $appPackagePath -Force -Confirm:$false;
return $true;
}
I think it would be possible to store the url in one of custom list in the app. Refer the url from the list. Whenever you need to change the url it can be done from the app itself.

How to access PowerShell host from C#

In a PowerShell profile, one can identify the PowerShell host in order to do appropriate setup for that host's environment. For example:
if ($host.Name -eq 'ConsoleHost')
{
Import-Module PSReadline
# differentiate verbose from warnings!
$privData = (Get-Host).PrivateData
$privData.VerboseForegroundColor = "cyan"
}
elseif ($host.Name -like '*ISE Host')
{
Start-Steroids
Import-Module PsIseProjectExplorer
}
I would like to be able to do the equivalent identification from a C# context primarily because PowerShell ISE does not support Console.ReadLine so I want to know if it is safe to use it in the current PS host's environment.
I first explored trying to get the output of the Get-Host cmdlet from within C# (per Invoking a cmdlet within a cmdlet). After I located the Microsoft.PowerShell.Commands.Utility assembly (under C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0) I could compile this but it yielded null...
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke();
...while this would not compile due to the InternalHost class being (ironically!) internal:
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke<System.Management.Automation.Internal.Host.InternalHost>();
Next, I then modified my cmdlet to inherit from PSCmdlet rather than Cmdlet (to allow access to the SessionState), so I could then access the PS host object like this:
var psVarObject = SessionState.PSVariable.GetValue("Host");
Of course, that returns a pure Object, which I then needed to cast to... oh, wait... it's still internal!... so this would not compile:
string psHost = ((System.Management.Automation.Internal.Host.InternalHost)psVarObject).Name;
Leaving me no alternative but to use reflection on a foreign assembly (horrors!):
string psHost = (string)psVarObject.GetType().GetProperty("Name").GetValue(psVarObject, null);
That works, but is less than ideal, because reflecting upon any 3rd-party assembly is a fragile thing to do.
Any alternative ideas on either (a) identifying the host or, (b) backing up a bit, being able to use the host's own Read-Host cmdlet to get a typed input from a user?
You can just use Host property from PSCmdlet class. And if you want to do Read-Host:
Host.UI.ReadLine()
When getting
var psVarObject = SessionState.PSVariable.GetValue("Host");
You can cast it to System.Management.Automation.Host.PSHost instead of InternalHost

Identify processes that have placed locks on a DLL using Powershell

Using powershell, I want to identify any process locks placed a given DLL.
Solved. See below.
function IsDLLFree()
{
# The list of DLLs to check for locks by running processes.
$DllsToCheckForLocks = "C:\mydll1.dll","C:\mydll2.dll";
# Assume true, then check all process dependencies
$result = $true;
# Iterate through each process and check module dependencies
foreach ($p in Get-Process)
{
# Iterate through each dll used in a given process
foreach ($m in Get-Process -Name $p.ProcessName -Module -ErrorAction SilentlyContinue)
{
# Check if dll dependency matches any DLLs in list
foreach ($dll in $DllsToCheckForLocks)
{
# Compare the fully-qualified file paths,
# if there's a match then a lock exists.
if ( ($m.FileName.CompareTo($dll) -eq 0) )
{
$pName = $p.ProcessName.ToString()
Write-Error "$dll is locked by $pName. Stop this service to release this lock on $m1."
$result = $false;
}
}
}
}
return $result;
}
This works if you're assessing dll files loaded in the current application domain. If you pass in the path to the dll file it will return whether or not that assemblies is loaded in the current application domain. This is particularly useful even if you don't know the .dll file (still works for that), but want to know if a general area has .dll files with locks.
function Get-IsPathUsed()
{
param([string]$Path)
$isUsed = $false
[System.AppDomain]::CurrentDomain.GetAssemblies() |? {$_.Location -like "*$Path*"} |% {
$isUsed = $true;
}
$isUsed;
}

Categories

Resources