Access hidden dynamic Property in COM-object with C# - c#

I need to access a specific property inside a COM object (the iTunes COM Library). You can access this property with the dynamic view of the Visual Studio debugger.
I tried to get this property using Reflection but I don't get any private properties or fields back.
I can access all the Properties that I also see in the debugger using this line:
new Microsoft.CSharp.RuntimeBinder.DynamicMetaObjectProviderDebugView(myObject).Items
However, I would rather not use this call because I believe an easier solution exists.
If you have iTunes installed this would be a simple example of what I'm trying to achieve:
iTunesAppClass app;
if (Process.GetProcessesByName("iTunes").Any())
{
app = new iTunesAppClass();
}
else
{
return;
}
foreach (IITPlaylist playlist in app.LibrarySource.Playlists)
{
// This does not work. There is no "Parent".
//var parent = playlist.Parent;
Type playListType = playlist.GetType();
// both contain 0 results
var fields = playListType.GetFields(BindingFlags.NonPublic);
var properties = playListType.GetFields(BindingFlags.NonPublic);
// works but only during runtime
//var parent2 = new Microsoft.CSharp.RuntimeBinder.DynamicMetaObjectProviderDebugView(playlist).Items[4];
}

Related

Solidworks C# can't extract item from feature that has TypeName equals "Reference"

i have opened solidworks assembly (swDocumentTypes_e.swDocASSEMBLY) using C# and i have iterated through all the features in order to get all the Sketchs called 'ISO/XXX' under each part of the assembly, here is the code
public void openFile(string skeletonFilePath)
{
object[] Features = null;
int i = 0;
string FeatType = null;[1]
string FeatTypeName = null;
if (string.IsNullOrWhiteSpace(skeletonFilePath)) { return; }
ModelDoc2 model = _sldWorks.OpenDoc("C:PATH/fileName.SLDASM", (int)swDocumentTypes_e.swDocASSEMBLY);
Feature swFeat = default(Feature);
SelectionMgr swSelMgr = default(SelectionMgr);
swSelMgr = (SelectionMgr)model.SelectionManager;
swFeat = (Feature)model.FirstFeature();
while ((swFeat != null))
{
FeatType = swFeat.Name;
FeatTypeName = swFeat.GetTypeName2();
if ((FeatTypeName == "Reference")
{
Debug.Print(" Name of feature: " + swFeat.Name);
Debug.Print(" Type of feature: " + swFeat.GetTypeName2());
}
swFeat = (Feature)swFeat.GetNextFeature();
}
}
the problem:
each time i try to extract the items under the feature (of one part) i got an exception, i have to tried these ways:
swFeat.GetDefinition() // i've got null exception
swFeat.GetSpecificFeature2() // i've got dynamic value which i don't know the class i need to cast with
var childs = (Object[])swFeatSupport.GetChildren(); // i've got only to constraints under the part
example of project
Your code is only iterating over top level features. You can use IFeature::GetFirstSubFeature() and IFeature::GetNextSubFeature to get sub feature. Make this function recursive so it will iterate over all features, regardless of how many levels deep they are. Another layer you need to consider is the components - you need to iterate over components in an assembly first if you need feature data in the context of individual parts.
Here's an example from the Solidworks API documentation. Its poorly written (IMO) but it will guide you in the right direction.

Remove metadata from Excel file using C#?

I'm currently using C# to set the custom attributes of multiple excel files. I'm using an imported library from Microsoft known as DSOFile to write to the CustomProperties property. One issue that I'm running into is whenever the code attempts to write to an excel file that already has custom properties written to it, such as the Company and Year, a COMException exception is thrown to indicate the custom properties of the file already has a field with that name. Exact Message: "An item by that name already exists in the collection". I would like to be able to delete that item in the collection so that I can rewrite to the file. For example, if I accidentally added the wrong year to the year attribute in the file, I would like the ability to clear that field and write a new value to it. I was unable to find a method in the DSOFile class that removes metadata. Is there anyway to "programmatically" clear metadata from a file without doing it through the file properties window?
Sample Code:
DSOFILE.OleDocumentProperties dso = new DSOFile.OleDocumentProperties();
dso.Open(#"c\temp\test.xls", false, DSOFile.dsoFileOpenOptions.dsoOptionDefault);
//add metadata
dso.CustomProperties.Add("Company", "Sony");
dso.Save();
dso.Close(false);
If you want to change the default properties used by Office like Company or Author, you can just update them via the SummaryProperties object:
OleDocumentProperties dso = new DSOFile.OleDocumentProperties();
dso.Open(#"c:\temp\test.xls", false, DSOFile.dsoFileOpenOptions.dsoOptionDefault);
//Update Company
dso.SummaryProperties.Company = "Hello World!";
dso.Save();
dso.Close(false);
Note, that you cannot change the default properties of documents that you can access via the SummaryProperties object via the CustomProperties object in dso. The CustomProperties are meant for additional properties used by the user, not the ones already introduced by Microsoft Office.
In order to change the custom properties, you have to be aware that CustomProperties is a collection that you can iterate over via foreach. So you can use the following two methods:
private static void DeleteProperty(CustomProperties properties, string propertyName)
{
foreach(CustomProperty property in properties)
{
if (string.Equals(property.Name, propertyName, StringComparison.InvariantCultureIgnoreCase))
{
property.Remove();
break;
}
}
}
private static void UpdateProperty(CustomProperties properties, string propertyName, string newValue)
{
bool propertyFound = false;
foreach (CustomProperty property in properties)
{
if (string.Equals(property.Name, propertyName, StringComparison.InvariantCultureIgnoreCase))
{
// Property found, change it
property.set_Value(newValue);
propertyFound = true;
break;
}
}
if(!propertyFound)
{
// The property with the given name was not found, so we have to add it
properties.Add(propertyName, newValue);
}
}
Here is an example on how to use UpdateProperty:
static void Main(string[] args)
{
OleDocumentProperties dso = new DSOFile.OleDocumentProperties();
dso.Open(#"c:\temp\test.xls", false, DSOFile.dsoFileOpenOptions.dsoOptionDefault);
UpdateProperty(dso.CustomProperties, "Year", "2017");
dso.Save();
dso.Close(false);
}

How to modify MS Access database Properties collection (not data!) from a C# program?

What is the best way to access Microsoft Access Database object's Properties (like in CurrentDb.Properties) from C# in Visual Studio 2010?
(Not essential: In fact I want to get rid of Replication in a few dozen databases "on demand". Replication is not in use for a few years, and it was OK for MS Access prior to 2013. Access 2013 rejects databases with this feature.)
You can iterate over and modify the properties in the Access database by using the Access DAO object library.
The following code iterates over the properties in the database as well over the different containers and its properties as well over the Documents and its properties in the Databases container. The output is written to the Debug Output window.
After that I pick a Property from different property collections and change it's value. Do note that using the indexer on the collection will throw an exception if the property doesn't exist.
Make sure you have a reference to the Primary Interop Assembly for Microsoft Office 12.0 Access database engine Object Library (your version migth vary) so that you can have the following in your using statements:
using Microsoft.Office.Interop.Access.Dao;
Your method would go like this:
// Open a database
var dbe = new DBEngine();
var db = dbe.OpenDatabase(#"C:\full\path\to\your\db\scratch.accdb");
// Show database properties
DumpProperties(db.Properties);
// show all containers
foreach (Container c in db.Containers)
{
Debug.WriteLine("{0}:{1}", c.Name, c.Owner);
DumpProperties(c.Properties);
}
//Show documents and properties for a specific container
foreach (Document d in db.Containers["Databases"].Documents)
{
Debug.WriteLine("--------- " + d.Name);
DumpProperties(d.Properties);
}
// set a property on the Database
Property prop = db.
Properties["NavPane Width"];
prop.Value = 300;
// set a property on the UserDefined document
Property userdefProp = db
.Containers["Databases"]
.Documents["UserDefined"]
.Properties["ReplicateProject"];
userdefProp.Value = true;
Property dumper helper
private static void DumpProperties(Properties props)
{
foreach (Property p in props)
{
object val = null;
try
{
val = (object)p.Value;
}
catch (Exception e)
{
val = e.Message;
}
Debug.WriteLine(
"{0} ({2}) = {1}",
p.Name,
val,
(DataTypeEnum) p.Type);
}
}
I used this to overcome an exception being thrown on dynamic types (as the Value property turns out to be)

NullReferenceException when deleting a sitecore items children

i'm trying to import some legacy poll data into our sitecore solution. For part of the import, i'm trying to create new sitecore items to hold the data based off of a master page we already have set up for polls. this master has a couple of default items set up underneath of it, and I want to delete a couple of specific default items before I add the legacy poll data. however, when i attempt to delete one of the default items using item.DeleteChildren(), i get a NullReferenceException thrown by Sitecore.Tasks.ItemEventHandler.OnItemDeleted(Object sender, EventArgs args) in the sitecore kernel. if anyone has any idea what could be causing this, i'd appreciate it. we're on sitecore version 5.3.2.
here is the code i'm using to attempt to create/edit the item based off of a master. the creation all works perfectly, it's the DeleteChildren() call that doesn't work.
Guid LegacyPollFolderGuid = new Guid("8AE89A44-9DCD-4AC2-B0F3-DD438188A575");
Guid QuizOMaticMasterGuid = new Guid("74B95ABF-1898-4870-8B4F-50AF0078AE22");
var master = Sitecore.Configuration.Factory.GetDatabase("master");
var root = master.GetItem(new Sitecore.Data.ID(LegacyPollFolderGuid));
var quizMasterTemplate = master.Masters[new Sitecore.Data.ID(QuizOMaticMasterGuid)];
var quizPage = root.Add("Test Quiz", quizMasterTemplate);
if (quizPage != null)
{
var quiz = quizPage.Children["Column One"].Children["QuizOMatic"];
if (quiz != null)
{
var questionFolder = quiz.Children["Questions"];
var questionTemplate = questionFolder.Children[0].Template;
var resultsFolder = quiz.Children["Results"];
var linksFolder = quiz.Children["Links"];
using (new Sitecore.SecurityModel.SecurityDisabler())
{
questionFolder.DeleteChildren();
}
}
I've built a tool with the same functionality; removing all items under a specific folder. I got the same error, but I saw items being deleted. So I reran the tool multiple times and eventually all items were deleted.

Determining the version of an MSI without installing it

I have an MSI file built from my C# Visual Studio 2010. The version is set through the Version property. I wanted to know if there is a way to determine the version without having to install the file. Currently when right click and view the properties it isn't displayed.
The following code may be helpful. But remember that you should first add a COM reference to the Microsoft Windows Installer Object Library and add the WindowsInstaller namespace to your code. The following function may be what you need.
public static string GetMsiInfo( string msiPath, string Info)
{
string retVal = string.Empty;
Type classType = Type.GetTypeFromProgID( “WindowsInstaller.Installer” );
Object installerObj = Activator.CreateInstance( classType );
Installer installer = installerObj as Installer;
// Open msi file
Database db = installer.OpenDatabase( msiPath, 0 );
// Fetch the property
string sql = String.Format(“SELECT Value FROM Property WHERE Property=’{0}’”, Info);
View view = db.OpenView( sql );
view.Execute( null );
// Read in the record
Record rec = view.Fetch();
if ( rec != null )
retVal = rec.get_StringData( 1 );
return retVal;
}
If you need the version, pass in the name of the MSI file you want, e.g.
string version = GetMsiInfo( "d:\product.msi", “ProductVersion” );
Yes - I think you need to inspect the MSI database however, which requires either some API calls or a wrapper utility.
Microsofts ORCA application should let you do this (although I've never tried it myself).
Instead of using the COM library, you can use the Microsoft.Deployment.WindowsInstaller library from the wixtoolset SDK. Once referenced, you can very similarly get the version info.
private string GetMsiInfo(string msiPath)
{
using (var database = new Microsoft.Deployment.WindowsInstaller.Database(msiPath))
{
var sql = "SELECT Value FROM Property WHERE Property ='ProductVersion'";
using (var view = database.OpenView(sql))
{
view.Execute();
using (var record = view.Fetch())
{
var version = record?.GetString(1);
return version;
}
}
}
}
I haven't found a way to get the correct assembly via nuget installer. However, after I installed the wixtoolset https://wixtoolset.org/releases/, I was able to add a reference in my project directly under assemblies -> extensions -> Microsoft.Deployment.WindowsInstaller.
Based on Gupta's answer, I added COM release calls. If you want to recreate or replace the file you accessed in your further workflow, it might be still in use and you will get an exception if the GC did not yet release the objects, so let's do this manually.
public static string GetMsiInfo(string msiPath, string info)
{
string retVal = string.Empty;
Type classType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
dynamic installer = Activator.CreateInstance(classType);
try
{
// Open msi file
var db = installer.OpenDatabase(msiPath, 0);
try
{
// Fetch the property
string sql = $"SELECT Value FROM Property WHERE Property ='{info}'";
var view = db.OpenView(sql);
try
{
view.Execute(null);
// Read in the record
var rec = view.Fetch();
if (rec != null)
retVal = rec.StringData(1);
return retVal;
}
finally
{
view.Close();
Marshal.ReleaseComObject(view);
}
}
finally
{
//db.Commit();
Marshal.ReleaseComObject(db);
}
}
finally
{
Marshal.ReleaseComObject(installer);
}
}
I think using this code, there is no need to add a COM reference or an extra namespace as mentioned by Gupta, because we use late binding here (see the dynamic).

Categories